Просмотр исходного кода

Merge branch 'bdb_dev' into Crowdin

Jon B.M 3 лет назад
Родитель
Сommit
e2396e1288
31 измененных файлов с 620 добавлено и 76 удалено
  1. 41 9
      FreeAPS.xcodeproj/project.pbxproj
  2. 2 2
      FreeAPS/Resources/Base.lproj/InfoPlist.strings
  3. 3 3
      FreeAPS/Resources/Info.plist
  4. 2 2
      FreeAPS/Resources/ar.lproj/InfoPlist.strings
  5. 6 1
      FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json
  6. 2 2
      FreeAPS/Resources/sv.lproj/InfoPlist.strings
  7. 10 2
      FreeAPS/Sources/APS/APSManager.swift
  8. 1 1
      FreeAPS/Sources/APS/CGM/DexcomSourceG6.swift
  9. 1 3
      FreeAPS/Sources/APS/DeviceDataManager.swift
  10. 33 1
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  11. 6 0
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  12. 3 0
      FreeAPS/Sources/Models/CarbsEntry.swift
  13. 25 0
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  14. 42 4
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift
  15. 38 8
      FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift
  16. 9 4
      FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift
  17. 9 5
      FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift
  18. 16 6
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift
  19. 2 2
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  20. 5 0
      FreeAPS/Sources/Modules/FPUConfig/FPUConfigDataFlow.swift
  21. 3 0
      FreeAPS/Sources/Modules/FPUConfig/FPUConfigProvider.swift
  22. 44 0
      FreeAPS/Sources/Modules/FPUConfig/FPUConfigStateModel.swift
  23. 64 0
      FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift
  24. 1 1
      FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift
  25. 1 1
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  26. 0 3
      FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift
  27. 1 0
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  28. 3 0
      FreeAPS/Sources/Router/Screen.swift
  29. 238 15
      FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift
  30. 1 1
      FreeAPS/Sources/Services/WatchManager/WatchManager.swift
  31. 8 0
      fastlane/testflight.md

+ 41 - 9
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 */,
@@ -2732,7 +2764,7 @@
 			buildSettings = {
 			buildSettings = {
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
 				APP_GROUP_ID = "$(APP_GROUP_ID)";
 				APP_GROUP_ID = "$(APP_GROUP_ID)";
-				ASSETCATALOG_COMPILER_APPICON_NAME = "$(APP_ICON)";
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon_BW;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				CODE_SIGN_ENTITLEMENTS = FreeAPS/Resources/FreeAPS.entitlements;
 				CODE_SIGN_ENTITLEMENTS = FreeAPS/Resources/FreeAPS.entitlements;
@@ -2768,7 +2800,7 @@
 			buildSettings = {
 			buildSettings = {
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
 				APP_GROUP_ID = "$(APP_GROUP_ID)";
 				APP_GROUP_ID = "$(APP_GROUP_ID)";
-				ASSETCATALOG_COMPILER_APPICON_NAME = "$(APP_ICON)";
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon_BW;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				CODE_SIGN_ENTITLEMENTS = FreeAPS/Resources/FreeAPS.entitlements;
 				CODE_SIGN_ENTITLEMENTS = FreeAPS/Resources/FreeAPS.entitlements;
@@ -2804,7 +2836,7 @@
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
-				ASSETCATALOG_COMPILER_APPICON_NAME = "$(APP_ICON)";
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon_BW;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
@@ -2834,7 +2866,7 @@
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
-				ASSETCATALOG_COMPILER_APPICON_NAME = "$(APP_ICON)";
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon_BW;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
@@ -2991,7 +3023,7 @@
 				388E596625AD948E0019842D /* Release */,
 				388E596625AD948E0019842D /* Release */,
 			);
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
+			defaultConfigurationName = Debug;
 		};
 		};
 		388E596725AD948E0019842D /* Build configuration list for PBXNativeTarget "FreeAPS" */ = {
 		388E596725AD948E0019842D /* Build configuration list for PBXNativeTarget "FreeAPS" */ = {
 			isa = XCConfigurationList;
 			isa = XCConfigurationList;
@@ -3000,7 +3032,7 @@
 				388E596925AD948E0019842D /* Release */,
 				388E596925AD948E0019842D /* Release */,
 			);
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
+			defaultConfigurationName = Debug;
 		};
 		};
 		38E8754327554D5900975559 /* Build configuration list for PBXNativeTarget "FreeAPSWatch WatchKit Extension" */ = {
 		38E8754327554D5900975559 /* Build configuration list for PBXNativeTarget "FreeAPSWatch WatchKit Extension" */ = {
 			isa = XCConfigurationList;
 			isa = XCConfigurationList;
@@ -3009,7 +3041,7 @@
 				38E8754227554D5900975559 /* Release */,
 				38E8754227554D5900975559 /* Release */,
 			);
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
+			defaultConfigurationName = Debug;
 		};
 		};
 		38E8754427554D5900975559 /* Build configuration list for PBXNativeTarget "FreeAPSWatch" */ = {
 		38E8754427554D5900975559 /* Build configuration list for PBXNativeTarget "FreeAPSWatch" */ = {
 			isa = XCConfigurationList;
 			isa = XCConfigurationList;
@@ -3018,7 +3050,7 @@
 				38E8753F27554D5900975559 /* Release */,
 				38E8753F27554D5900975559 /* Release */,
 			);
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
+			defaultConfigurationName = Debug;
 		};
 		};
 		38FCF3F425E9028E0078B0D1 /* Build configuration list for PBXNativeTarget "FreeAPSTests" */ = {
 		38FCF3F425E9028E0078B0D1 /* Build configuration list for PBXNativeTarget "FreeAPSTests" */ = {
 			isa = XCConfigurationList;
 			isa = XCConfigurationList;
@@ -3027,7 +3059,7 @@
 				38FCF3F625E9028E0078B0D1 /* Release */,
 				38FCF3F625E9028E0078B0D1 /* Release */,
 			);
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
+			defaultConfigurationName = Debug;
 		};
 		};
 /* End XCConfigurationList section */
 /* End XCConfigurationList section */
 
 

+ 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";

+ 3 - 3
FreeAPS/Resources/Info.plist

@@ -71,11 +71,11 @@
 	<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>$(BRANCH)</string>
+	<string>$(COPYRIGHT_NOTICE)</string>
 	<key>UIApplicationSceneManifest</key>
 	<key>UIApplicationSceneManifest</key>
 	<dict>
 	<dict>
 		<key>UIApplicationSupportsMultipleScenes</key>
 		<key>UIApplicationSupportsMultipleScenes</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";

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

@@ -21,5 +21,10 @@
     "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,
+    "delay": 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.";

+ 10 - 2
FreeAPS/Sources/APS/APSManager.swift

@@ -72,6 +72,7 @@ final class BaseAPSManager: APSManager, Injectable {
     @Injected() private var nightscout: NightscoutManager!
     @Injected() private var nightscout: NightscoutManager!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var broadcaster: Broadcaster!
+    @Injected() private var healthKitManager: HealthKitManager!
     @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date()
     @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date()
     @Persisted(key: "lastStartLoopDate") private var lastStartLoopDate: Date = .distantPast
     @Persisted(key: "lastStartLoopDate") private var lastStartLoopDate: Date = .distantPast
     @Persisted(key: "lastLoopDate") var lastLoopDate: Date = .distantPast {
     @Persisted(key: "lastLoopDate") var lastLoopDate: Date = .distantPast {
@@ -250,6 +251,10 @@ final class BaseAPSManager: APSManager, Injectable {
     private func loopCompleted(error: Error? = nil, loopStatRecord: LoopStats) {
     private func loopCompleted(error: Error? = nil, loopStatRecord: LoopStats) {
         isLooping.send(false)
         isLooping.send(false)
 
 
+        // save AH events
+        let events = pumpHistoryStorage.recent()
+        healthKitManager.saveIfNeeded(pumpEvents: events)
+
         if let error = error {
         if let error = error {
             warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
             warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
             if let backgroundTask = backGroundTaskID {
             if let backgroundTask = backGroundTaskID {
@@ -895,6 +900,7 @@ final class BaseAPSManager: APSManager, Injectable {
         var timeIntervalLoopArray: [Double] = []
         var timeIntervalLoopArray: [Double] = []
         var medianInterval = 0.0
         var medianInterval = 0.0
         var averageIntervalLoops = 0.0
         var averageIntervalLoops = 0.0
+        var averageLoopDuration = 0.0
 
 
         coredataContext.performAndWait {
         coredataContext.performAndWait {
             let requestLSR = LoopStatRecord.fetchRequest() as NSFetchRequest<LoopStatRecord>
             let requestLSR = LoopStatRecord.fetchRequest() as NSFetchRequest<LoopStatRecord>
@@ -956,6 +962,8 @@ final class BaseAPSManager: APSManager, Injectable {
                 medianInterval = medianCalculation(array: timeIntervalLoopArray)
                 medianInterval = medianCalculation(array: timeIntervalLoopArray)
                 // Average time interval between loops
                 // Average time interval between loops
                 averageIntervalLoops = timeIntervalLoopArray.reduce(0, +) / Double(timeIntervalLoopArray.count)
                 averageIntervalLoops = timeIntervalLoopArray.reduce(0, +) / Double(timeIntervalLoopArray.count)
+                // Average loop duration
+                averageLoopDuration = timeForOneLoopArray.reduce(0, +) / Double(timeForOneLoopArray.count)
             }
             }
         }
         }
 
 
@@ -1217,11 +1225,11 @@ final class BaseAPSManager: APSManager, Injectable {
             errors: errorNR,
             errors: errorNR,
             readings: Int(nrOfCGMReadings),
             readings: Int(nrOfCGMReadings),
             success_rate: Decimal(round(successRate ?? 0)),
             success_rate: Decimal(round(successRate ?? 0)),
-            avg_interval: roundDecimal(Decimal(averageIntervalLoops), 1),
+            avg_interval: roundDecimal(Decimal(averageLoopTime), 1),
             median_interval: roundDecimal(Decimal(medianInterval), 1),
             median_interval: roundDecimal(Decimal(medianInterval), 1),
             min_interval: roundDecimal(Decimal(minimumInt), 1),
             min_interval: roundDecimal(Decimal(minimumInt), 1),
             max_interval: roundDecimal(Decimal(maximumInt), 1),
             max_interval: roundDecimal(Decimal(maximumInt), 1),
-            avg_duration: Decimal(roundDouble(averageLoopTime, 2)),
+            avg_duration: Decimal(roundDouble(averageLoopDuration, 2)),
             median_duration: Decimal(roundDouble(medianLoopTime, 2)),
             median_duration: Decimal(roundDouble(medianLoopTime, 2)),
             min_duration: roundDecimal(Decimal(minimumLoopTime), 2),
             min_duration: roundDecimal(Decimal(minimumLoopTime), 2),
             max_duration: Decimal(roundDouble(maximumLoopTime, 1))
             max_duration: Decimal(roundDouble(maximumLoopTime, 1))

+ 1 - 1
FreeAPS/Sources/APS/CGM/DexcomSourceG6.swift

@@ -96,7 +96,7 @@ extension DexcomSourceG6: CGMManagerDelegate {
     func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
     func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
 
 
     func cgmManagerWantsDeletion(_ manager: CGMManager) {
     func cgmManagerWantsDeletion(_ manager: CGMManager) {
-        dispatchPrecondition(condition: .onQueue(.main))
+        dispatchPrecondition(condition: .onQueue(processQueue))
         debug(.deviceManager, " CGM Manager with identifier \(manager.managerIdentifier) wants deletion")
         debug(.deviceManager, " CGM Manager with identifier \(manager.managerIdentifier) wants deletion")
         glucoseManager?.cgmGlucoseSourceType = nil
         glucoseManager?.cgmGlucoseSourceType = nil
     }
     }

+ 1 - 3
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -85,13 +85,12 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
             if let pumpManager = pumpManager {
             if let pumpManager = pumpManager {
                 pumpDisplayState.value = PumpDisplayState(name: pumpManager.localizedTitle, image: pumpManager.smallImage)
                 pumpDisplayState.value = PumpDisplayState(name: pumpManager.localizedTitle, image: pumpManager.smallImage)
                 pumpName.send(pumpManager.localizedTitle)
                 pumpName.send(pumpManager.localizedTitle)
-
+                pumpManager.setMustProvideBLEHeartbeat(heartbeatBypump)
                 if let omnipod = pumpManager as? OmnipodPumpManager {
                 if let omnipod = pumpManager as? OmnipodPumpManager {
                     guard let endTime = omnipod.state.podState?.expiresAt else {
                     guard let endTime = omnipod.state.podState?.expiresAt else {
                         pumpExpiresAtDate.send(nil)
                         pumpExpiresAtDate.send(nil)
                         return
                         return
                     }
                     }
-                    pumpManager.setMustProvideBLEHeartbeat(heartbeatBypump)
                     pumpExpiresAtDate.send(endTime)
                     pumpExpiresAtDate.send(endTime)
                 }
                 }
                 if let omnipodBLE = pumpManager as? OmniBLEPumpManager {
                 if let omnipodBLE = pumpManager as? OmniBLEPumpManager {
@@ -99,7 +98,6 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
                         pumpExpiresAtDate.send(nil)
                         pumpExpiresAtDate.send(nil)
                         return
                         return
                     }
                     }
-                    pumpManager.setMustProvideBLEHeartbeat(heartbeatBypump)
                     pumpExpiresAtDate.send(endTime)
                     pumpExpiresAtDate.send(endTime)
                 }
                 }
             } else {
             } else {

Разница между файлами не показана из-за своего большого размера
+ 33 - 1
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings


+ 6 - 0
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings

@@ -951,6 +951,12 @@ Enact a temp Basal or a temp target */
 /* Online or internal server */
 /* Online or internal server */
 "Online or internal server" = "Online eller intern server";
 "Online or internal server" = "Online eller intern server";
 
 
+/* Add Fat */
+"Fat" = "Fett";
+
+/* Add Protein */
+"Protein" = "Protein";
+
 /* -------------- Developer settings ---------------------- */
 /* -------------- Developer settings ---------------------- */
 /* Debug options */
 /* Debug options */
 
 

+ 3 - 0
FreeAPS/Sources/Models/CarbsEntry.swift

@@ -1,11 +1,13 @@
 import Foundation
 import Foundation
 
 
 struct CarbsEntry: JSON, Equatable, Hashable {
 struct CarbsEntry: JSON, Equatable, Hashable {
+    let id: String?
     let createdAt: Date
     let createdAt: Date
     let carbs: Decimal
     let carbs: Decimal
     let enteredBy: String?
     let enteredBy: String?
 
 
     static let manual = "freeaps-x"
     static let manual = "freeaps-x"
+    static let appleHealth = "applehealth"
 
 
     static func == (lhs: CarbsEntry, rhs: CarbsEntry) -> Bool {
     static func == (lhs: CarbsEntry, rhs: CarbsEntry) -> Bool {
         lhs.createdAt == rhs.createdAt
         lhs.createdAt == rhs.createdAt
@@ -18,6 +20,7 @@ struct CarbsEntry: JSON, Equatable, Hashable {
 
 
 extension CarbsEntry {
 extension CarbsEntry {
     private enum CodingKeys: String, CodingKey {
     private enum CodingKeys: String, CodingKey {
+        case id = "_id"
         case createdAt = "created_at"
         case createdAt = "created_at"
         case carbs
         case carbs
         case enteredBy
         case enteredBy

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

@@ -25,6 +25,11 @@ 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
+    var delay: Int = 60
 }
 }
 
 
 extension FreeAPSSettings: Decodable {
 extension FreeAPSSettings: Decodable {
@@ -97,6 +102,26 @@ 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 delay = try? container.decode(Int.self, forKey: .delay) {
+            settings.delay = delay
+        }
+
         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
         }
         }

+ 42 - 4
FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift

@@ -4,23 +4,61 @@ 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 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() {
-            guard carbs > 0 else {
+            guard carbs > 0 || fat > 0 || protein > 0 else {
                 showModal(for: nil)
                 showModal(for: nil)
                 return
                 return
             }
             }
 
 
-            carbsStorage.storeCarbs([
-                CarbsEntry(createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual)
-            ])
+            let interval = settings.settings.minuteInterval
+            let timeCap = settings.settings.timeCap * (60 / Decimal(interval))
+            let adjustment = settings.settings.individualAdjustmentFactor
+            let delay = settings.settings.delay
+
+            // 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
+            var counter: Decimal = (fpus * 2) - 1.0
+            counter = max(timeCap, counter)
+            var roundedCounter: Decimal = 0
+            NSDecimalRound(&roundedCounter, &counter, 0, .up)
+            let carbequiv = (fpucarb / roundedCounter) * adjustment
+            let firstDate = date.addingTimeInterval(delay.minutes.timeInterval)
+            var previousDate = date
+
+            while counter > 0, carbequiv > 0 {
+                var useDate = date + 1 * Double(interval * 60)
+                // Fix Interval and Delay
+                useDate = max(previousDate.addingTimeInterval(interval.minutes.timeInterval), useDate, firstDate)
+                if useDate > previousDate {
+                    carbsStorage.storeCarbs([
+                        CarbsEntry(
+                            id: UUID().uuidString, createdAt: useDate, carbs: carbequiv,
+                            enteredBy: CarbsEntry.manual
+                        )
+                    ])
+                }
+                previousDate = useDate
+                counter -= 1
+            }
+            // Store the real carbs
+            if carbs > 0 {
+                carbsStorage
+                    .storeCarbs([CarbsEntry(id: UUID().uuidString, createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual)])
+            }
 
 
             if settingsManager.settings.skipBolusScreenAfterCarbs {
             if settingsManager.settings.skipBolusScreenAfterCarbs {
                 apsManager.determineBasalSync()
                 apsManager.determineBasalSync()

+ 38 - 8
FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift

@@ -25,19 +25,49 @@ extension AddCarbs {
                     }
                     }
                 }
                 }
                 Section {
                 Section {
-                    HStack {
-                        Text("Amount")
-                        Spacer()
-                        DecimalTextField("0", value: $state.carbs, formatter: formatter, autofocus: true, cleanInput: true)
-                        Text("grams").foregroundColor(.secondary)
+                    Section {
+                        HStack {
+                            Text("Carbs").fontWeight(.semibold)
+                            Spacer()
+                            DecimalTextField("0", value: $state.carbs, formatter: formatter, autofocus: true, cleanInput: true)
+                            Text("grams").foregroundColor(.secondary)
+                        }.padding(.vertical)
+
+                        // 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)
                 }
                 }
-
                 Section {
                 Section {
                     Button { state.add() }
                     Button { state.add() }
                     label: { Text("Add") }
                     label: { Text("Add") }
-                        .disabled(state.carbs <= 0)
+                        .disabled(state.carbs <= 0 && state.fat <= 0 && state.protein <= 0)
                 }
                 }
             }
             }
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)

+ 9 - 4
FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift

@@ -52,7 +52,8 @@ enum DataTable {
     }
     }
 
 
     class Treatment: Identifiable, Hashable, Equatable {
     class Treatment: Identifiable, Hashable, Equatable {
-        let id = UUID()
+        let id: String
+        let idPumpEvent: String?
         let units: GlucoseUnits
         let units: GlucoseUnits
         let type: DataType
         let type: DataType
         let date: Date
         let date: Date
@@ -73,7 +74,9 @@ enum DataTable {
             date: Date,
             date: Date,
             amount: Decimal? = nil,
             amount: Decimal? = nil,
             secondAmount: Decimal? = nil,
             secondAmount: Decimal? = nil,
-            duration: Decimal? = nil
+            duration: Decimal? = nil,
+            id: String? = nil,
+            idPumpEvent: String? = nil
         ) {
         ) {
             self.units = units
             self.units = units
             self.type = type
             self.type = type
@@ -81,6 +84,8 @@ enum DataTable {
             self.amount = amount
             self.amount = amount
             self.secondAmount = secondAmount
             self.secondAmount = secondAmount
             self.duration = duration
             self.duration = duration
+            self.id = id ?? UUID().uuidString
+            self.idPumpEvent = idPumpEvent
         }
         }
 
 
         static func == (lhs: Treatment, rhs: Treatment) -> Bool {
         static func == (lhs: Treatment, rhs: Treatment) -> Bool {
@@ -172,7 +177,7 @@ protocol DataTableProvider: Provider {
     func tempTargets() -> [TempTarget]
     func tempTargets() -> [TempTarget]
     func carbs() -> [CarbsEntry]
     func carbs() -> [CarbsEntry]
     func glucose() -> [BloodGlucose]
     func glucose() -> [BloodGlucose]
-    func deleteCarbs(at date: Date)
-    func deleteInsulin(at date: Date)
+    func deleteCarbs(_ treatement: DataTable.Treatment)
+    func deleteInsulin(_ treatement: DataTable.Treatment)
     func deleteGlucose(id: String)
     func deleteGlucose(id: String)
 }
 }

+ 9 - 5
FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift

@@ -21,12 +21,16 @@ extension DataTable {
             carbsStorage.recent()
             carbsStorage.recent()
         }
         }
 
 
-        func deleteCarbs(at date: Date) {
-            nightscoutManager.deleteCarbs(at: date)
+        func deleteCarbs(_ treatement: Treatment) {
+            nightscoutManager.deleteCarbs(at: treatement.date)
+            healthkitManager.deleteCarbs(syncID: treatement.id)
         }
         }
 
 
-        func deleteInsulin(at date: Date) {
-            nightscoutManager.deleteInsulin(at: date)
+        func deleteInsulin(_ treatement: Treatment) {
+            nightscoutManager.deleteInsulin(at: treatement.date)
+            if let id = treatement.idPumpEvent {
+                healthkitManager.deleteInsulin(syncID: id)
+            }
         }
         }
 
 
         func glucose() -> [BloodGlucose] {
         func glucose() -> [BloodGlucose] {
@@ -35,7 +39,7 @@ extension DataTable {
 
 
         func deleteGlucose(id: String) {
         func deleteGlucose(id: String) {
             glucoseStorage.removeGlucose(ids: [id])
             glucoseStorage.removeGlucose(ids: [id])
-            healthkitManager.deleteGlucise(syncID: id)
+            healthkitManager.deleteGlucose(syncID: id)
         }
         }
     }
     }
 }
 }

+ 16 - 6
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -26,13 +26,23 @@ extension DataTable {
                 let units = self.settingsManager.settings.units
                 let units = self.settingsManager.settings.units
 
 
                 let carbs = self.provider.carbs().map {
                 let carbs = self.provider.carbs().map {
-                    Treatment(units: units, type: .carbs, date: $0.createdAt, amount: $0.carbs)
+                    if let id = $0.id {
+                        return Treatment(
+                            units: units,
+                            type: .carbs,
+                            date: $0.createdAt,
+                            amount: $0.carbs,
+                            id: id
+                        )
+                    } else {
+                        return Treatment(units: units, type: .carbs, date: $0.createdAt, amount: $0.carbs)
+                    }
                 }
                 }
 
 
                 let boluses = self.provider.pumpHistory()
                 let boluses = self.provider.pumpHistory()
                     .filter { $0.type == .bolus }
                     .filter { $0.type == .bolus }
                     .map {
                     .map {
-                        Treatment(units: units, type: .bolus, date: $0.timestamp, amount: $0.amount)
+                        Treatment(units: units, type: .bolus, date: $0.timestamp, amount: $0.amount, idPumpEvent: $0.id)
                     }
                     }
 
 
                 let tempBasals = self.provider.pumpHistory()
                 let tempBasals = self.provider.pumpHistory()
@@ -90,15 +100,15 @@ extension DataTable {
             }
             }
         }
         }
 
 
-        func deleteCarbs(at date: Date) {
-            provider.deleteCarbs(at: date)
+        func deleteCarbs(_ treatment: Treatment) {
+            provider.deleteCarbs(treatment)
         }
         }
 
 
-        func deleteInsulin(at date: Date) {
+        func deleteInsulin(_ treatment: Treatment) {
             unlockmanager.unlock()
             unlockmanager.unlock()
                 .sink { _ in } receiveValue: { [weak self] _ in
                 .sink { _ in } receiveValue: { [weak self] _ in
                     guard let self = self else { return }
                     guard let self = self else { return }
-                    self.provider.deleteInsulin(at: date)
+                    self.provider.deleteInsulin(treatment)
                 }
                 }
                 .store(in: &lifetime)
                 .store(in: &lifetime)
         }
         }

+ 2 - 2
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -94,7 +94,7 @@ extension DataTable {
                                 message: Text(item.amountText),
                                 message: Text(item.amountText),
                                 primaryButton: .destructive(
                                 primaryButton: .destructive(
                                     Text("Delete"),
                                     Text("Delete"),
-                                    action: { state.deleteCarbs(at: item.date) }
+                                    action: { state.deleteCarbs(item) }
                                 ),
                                 ),
                                 secondaryButton: .cancel()
                                 secondaryButton: .cancel()
                             )
                             )
@@ -116,7 +116,7 @@ extension DataTable {
                                 message: Text(item.amountText),
                                 message: Text(item.amountText),
                                 primaryButton: .destructive(
                                 primaryButton: .destructive(
                                     Text("Delete"),
                                     Text("Delete"),
-                                    action: { state.deleteInsulin(at: item.date) }
+                                    action: { state.deleteInsulin(item) }
                                 ),
                                 ),
                                 secondaryButton: .cancel()
                                 secondaryButton: .cancel()
                             )
                             )

+ 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 {}
+}

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

@@ -0,0 +1,44 @@
+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
+        @Published var delay: 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, 60), 10)
+                minuteInterval = Decimal(value)
+            }, map: {
+                $0
+            })
+
+            subscribeSetting(\.delay, on: $delay.map(Int.init), initial: {
+                let value = max(min($0, 120), 60)
+                delay = Decimal(value)
+            }, map: {
+                $0
+            })
+
+            subscribeSetting(\.individualAdjustmentFactor, on: $individualAdjustmentFactor, initial: {
+                let value = max(min($0, 1.2), 0.1)
+                individualAdjustmentFactor = value
+            }, map: {
+                $0
+            })
+        }
+    }
+}

Разница между файлами не показана из-за своего большого размера
+ 64 - 0
FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift


+ 1 - 1
FreeAPS/Sources/Modules/HealthKit/HealthKitStateModel.swift

@@ -35,7 +35,7 @@ extension AppleHealthKit {
 
 
                     debug(.service, "Permission  granted HealthKitManager")
                     debug(.service, "Permission  granted HealthKitManager")
 
 
-                    self.healthKitManager.createObserver()
+                    self.healthKitManager.createBGObserver()
                     self.healthKitManager.enableBackgroundDelivery()
                     self.healthKitManager.enableBackgroundDelivery()
                 }
                 }
             }
             }

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -93,7 +93,7 @@ struct MainChartView: View {
     private var carbsFormatter: NumberFormatter {
     private var carbsFormatter: NumberFormatter {
         let formatter = NumberFormatter()
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
         formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 0
+        formatter.maximumFractionDigits = 1
         return formatter
         return formatter
     }
     }
 
 

+ 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)
         }
         }

+ 238 - 15
FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift

@@ -1,6 +1,7 @@
 import Combine
 import Combine
 import Foundation
 import Foundation
 import HealthKit
 import HealthKit
+import LoopKit
 import LoopKitUI
 import LoopKitUI
 import Swinject
 import Swinject
 
 
@@ -14,21 +15,35 @@ protocol HealthKitManager: GlucoseSource {
     func requestPermission(completion: ((Bool, Error?) -> Void)?)
     func requestPermission(completion: ((Bool, Error?) -> Void)?)
     /// Save blood glucose to Health store (dublicate of bg will ignore)
     /// Save blood glucose to Health store (dublicate of bg will ignore)
     func saveIfNeeded(bloodGlucose: [BloodGlucose])
     func saveIfNeeded(bloodGlucose: [BloodGlucose])
+    /// Save carbs to Health store (dublicate of bg will ignore)
+    func saveIfNeeded(carbs: [CarbsEntry])
+    /// Save Insulin to Health store
+    func saveIfNeeded(pumpEvents events: [PumpHistoryEvent])
     /// Create observer for data passing beetwen Health Store and FreeAPS
     /// Create observer for data passing beetwen Health Store and FreeAPS
-    func createObserver()
+    func createBGObserver()
     /// Enable background delivering objects from Apple Health to FreeAPS
     /// Enable background delivering objects from Apple Health to FreeAPS
     func enableBackgroundDelivery()
     func enableBackgroundDelivery()
     /// Delete glucose with syncID
     /// Delete glucose with syncID
-    func deleteGlucise(syncID: String)
+    func deleteGlucose(syncID: String)
+    /// delete carbs with syncID
+    func deleteCarbs(syncID: String)
+    /// delete insulin with syncID
+    func deleteInsulin(syncID: String)
 }
 }
 
 
-final class BaseHealthKitManager: HealthKitManager, Injectable {
+final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
     private enum Config {
     private enum Config {
         // unwraped HKObjects
         // unwraped HKObjects
-        static var permissions: Set<HKSampleType> { Set([healthBGObject].compactMap { $0 }) }
+        static var readPermissions: Set<HKSampleType> {
+            Set([healthBGObject].compactMap { $0 }) }
+
+        static var writePermissions: Set<HKSampleType> {
+            Set([healthBGObject, healthCarbObject, healthInsulinObject].compactMap { $0 }) }
 
 
         // link to object in HealthKit
         // link to object in HealthKit
         static let healthBGObject = HKObjectType.quantityType(forIdentifier: .bloodGlucose)
         static let healthBGObject = HKObjectType.quantityType(forIdentifier: .bloodGlucose)
+        static let healthCarbObject = HKObjectType.quantityType(forIdentifier: .dietaryCarbohydrates)
+        static let healthInsulinObject = HKObjectType.quantityType(forIdentifier: .insulinDelivery)
 
 
         // Meta-data key of FreeASPX data in HealthStore
         // Meta-data key of FreeASPX data in HealthStore
         static let freeAPSMetaKey = "fromFreeAPSX"
         static let freeAPSMetaKey = "fromFreeAPSX"
@@ -37,6 +52,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var healthKitStore: HKHealthStore!
     @Injected() private var healthKitStore: HKHealthStore!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var settingsManager: SettingsManager!
+    @Injected() private var broadcaster: Broadcaster!
 
 
     private let processQueue = DispatchQueue(label: "BaseHealthKitManager.processQueue")
     private let processQueue = DispatchQueue(label: "BaseHealthKitManager.processQueue")
     private var lifetime = Lifetime()
     private var lifetime = Lifetime()
@@ -47,22 +63,25 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
     // last anchor for HKAnchoredQuery
     // last anchor for HKAnchoredQuery
     private var lastBloodGlucoseQueryAnchor: HKQueryAnchor? {
     private var lastBloodGlucoseQueryAnchor: HKQueryAnchor? {
         set {
         set {
-            persistedAnchor = try? NSKeyedArchiver.archivedData(withRootObject: newValue as Any, requiringSecureCoding: false)
+            persistedBGAnchor = try? NSKeyedArchiver.archivedData(withRootObject: newValue as Any, requiringSecureCoding: false)
         }
         }
         get {
         get {
-            guard let data = persistedAnchor else { return nil }
+            guard let data = persistedBGAnchor else { return nil }
             return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? HKQueryAnchor
             return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? HKQueryAnchor
         }
         }
     }
     }
 
 
-    @Persisted(key: "HealthKitManagerAnchor") private var persistedAnchor: Data? = nil
+    @Persisted(key: "HealthKitManagerAnchor") private var persistedBGAnchor: Data? = nil
 
 
     var isAvailableOnCurrentDevice: Bool {
     var isAvailableOnCurrentDevice: Bool {
         HKHealthStore.isHealthDataAvailable()
         HKHealthStore.isHealthDataAvailable()
     }
     }
 
 
     var areAllowAllPermissions: Bool {
     var areAllowAllPermissions: Bool {
-        Set(Config.permissions.map { healthKitStore.authorizationStatus(for: $0) })
+        Set(Config.readPermissions.map { healthKitStore.authorizationStatus(for: $0) })
+            .intersection([.notDetermined])
+            .isEmpty &&
+            Set(Config.writePermissions.map { healthKitStore.authorizationStatus(for: $0) })
             .intersection([.sharingDenied, .notDetermined])
             .intersection([.sharingDenied, .notDetermined])
             .isEmpty
             .isEmpty
     }
     }
@@ -91,8 +110,11 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
         injectServices(resolver)
         injectServices(resolver)
         guard isAvailableOnCurrentDevice,
         guard isAvailableOnCurrentDevice,
               Config.healthBGObject != nil else { return }
               Config.healthBGObject != nil else { return }
-        createObserver()
+        createBGObserver()
         enableBackgroundDelivery()
         enableBackgroundDelivery()
+
+        broadcaster.register(CarbsObserver.self, observer: self)
+
         debug(.service, "HealthKitManager did create")
         debug(.service, "HealthKitManager did create")
     }
     }
 
 
@@ -109,12 +131,12 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
             completion?(false, HKError.notAvailableOnCurrentDevice)
             completion?(false, HKError.notAvailableOnCurrentDevice)
             return
             return
         }
         }
-        guard Config.permissions.isNotEmpty else {
+        guard Config.readPermissions.isNotEmpty, Config.writePermissions.isNotEmpty else {
             completion?(false, HKError.dataNotAvailable)
             completion?(false, HKError.dataNotAvailable)
             return
             return
         }
         }
 
 
-        healthKitStore.requestAuthorization(toShare: Config.permissions, read: Config.permissions) { status, error in
+        healthKitStore.requestAuthorization(toShare: Config.writePermissions, read: Config.readPermissions) { status, error in
             completion?(status, error)
             completion?(status, error)
         }
         }
     }
     }
@@ -154,7 +176,130 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
             .store(in: &lifetime)
             .store(in: &lifetime)
     }
     }
 
 
-    func createObserver() {
+    func saveIfNeeded(carbs: [CarbsEntry]) {
+        guard settingsManager.settings.useAppleHealth,
+              let sampleType = Config.healthCarbObject,
+              checkAvailabilitySave(objectTypeToHealthStore: sampleType),
+              carbs.isNotEmpty
+        else { return }
+
+        let carbsWithId = carbs.filter { c in
+            guard c.id != nil else { return false }
+            return true
+        }
+
+        func save(samples: [HKSample]) {
+            let sampleIDs = samples.compactMap(\.syncIdentifier)
+            let sampleDates = samples.map(\.startDate)
+            let samplesToSave = carbsWithId
+                .filter { !sampleIDs.contains($0.id!) } // id existing in AH
+                .filter { !sampleDates.contains($0.createdAt) } // not id but exaclty the same datetime
+                .map {
+                    HKQuantitySample(
+                        type: sampleType,
+                        quantity: HKQuantity(unit: .gram(), doubleValue: Double($0.carbs)),
+                        start: $0.createdAt,
+                        end: $0.createdAt,
+                        metadata: [
+                            HKMetadataKeyExternalUUID: $0.id ?? "_id",
+                            HKMetadataKeySyncIdentifier: $0.id ?? "_id",
+                            HKMetadataKeySyncVersion: 1,
+                            Config.freeAPSMetaKey: true
+                        ]
+                    )
+                }
+
+            healthKitStore.save(samplesToSave) { _, _ in }
+        }
+
+        loadSamplesFromHealth(sampleType: sampleType)
+            .receive(on: processQueue)
+            .sink(receiveValue: save)
+            .store(in: &lifetime)
+    }
+
+    func saveIfNeeded(pumpEvents events: [PumpHistoryEvent]) {
+        guard settingsManager.settings.useAppleHealth,
+              let sampleType = Config.healthInsulinObject,
+              checkAvailabilitySave(objectTypeToHealthStore: sampleType),
+              events.isNotEmpty
+        else { return }
+
+        func save(bolus: [InsulinBolus], basal: [InsulinBasal]) {
+            let bolusSamples = bolus
+                .map {
+                    HKQuantitySample(
+                        type: sampleType,
+                        quantity: HKQuantity(unit: .internationalUnit(), doubleValue: Double($0.amount)),
+                        start: $0.date,
+                        end: $0.date,
+                        metadata: [
+                            HKMetadataKeyInsulinDeliveryReason: NSNumber(2),
+                            HKMetadataKeyExternalUUID: $0.id,
+                            HKMetadataKeySyncIdentifier: $0.id,
+                            HKMetadataKeySyncVersion: 1,
+                            Config.freeAPSMetaKey: true
+                        ]
+                    )
+                }
+
+            let basalSamples = basal
+                .map {
+                    HKQuantitySample(
+                        type: sampleType,
+                        quantity: HKQuantity(unit: .internationalUnit(), doubleValue: Double($0.amount)),
+                        start: $0.startDelivery,
+                        end: $0.endDelivery,
+                        metadata: [
+                            HKMetadataKeyInsulinDeliveryReason: NSNumber(1),
+                            HKMetadataKeyExternalUUID: $0.id,
+                            HKMetadataKeySyncIdentifier: $0.id,
+                            HKMetadataKeySyncVersion: 1,
+                            Config.freeAPSMetaKey: true
+                        ]
+                    )
+                }
+
+            healthKitStore.save(bolusSamples + basalSamples) { _, _ in }
+        }
+
+        loadSamplesFromHealth(sampleType: sampleType, withIDs: events.map(\.id))
+            .receive(on: processQueue)
+            .compactMap { samples -> ([InsulinBolus], [InsulinBasal]) in
+                let sampleIDs = samples.compactMap(\.syncIdentifier)
+                let bolus = events
+                    .filter { $0.type == .bolus && !sampleIDs.contains($0.id) }
+                    .compactMap { event -> InsulinBolus? in
+                        guard let amount = event.amount else { return nil }
+                        return InsulinBolus(id: event.id, amount: amount, date: event.timestamp)
+                    }
+                let basalEvents = events
+                    .filter { $0.type == .tempBasal && !sampleIDs.contains($0.id) }
+                let basal = basalEvents.enumerated()
+                    .compactMap { item -> InsulinBasal? in
+                        let nextElementEventIndex = item.offset + 1
+                        guard basalEvents.count > nextElementEventIndex else { return nil }
+                        let nextBasalEvent = basalEvents[nextElementEventIndex]
+                        let secondsOfCurrentBasal = nextBasalEvent.timestamp.timeIntervalSince(item.element.timestamp)
+                        let amount = Decimal(secondsOfCurrentBasal / 3600) * (item.element.rate ?? 0)
+                        let id = String(item.element.id.dropFirst())
+                        guard amount > 0,
+                              id != ""
+                        else { return nil }
+                        return InsulinBasal(
+                            id: id,
+                            amount: amount,
+                            startDelivery: item.element.timestamp,
+                            endDelivery: nextBasalEvent.timestamp
+                        )
+                    }
+                return (bolus, basal)
+            }
+            .sink(receiveValue: save)
+            .store(in: &lifetime)
+    }
+
+    func createBGObserver() {
         guard settingsManager.settings.useAppleHealth else { return }
         guard settingsManager.settings.useAppleHealth else { return }
 
 
         guard let bgType = Config.healthBGObject else {
         guard let bgType = Config.healthBGObject else {
@@ -201,6 +346,23 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
         }
         }
     }
     }
 
 
+    /// Try to load samples from Health store
+    private func loadSamplesFromHealth(
+        sampleType: HKQuantityType
+    ) -> Future<[HKSample], Never> {
+        Future { promise in
+            let query = HKSampleQuery(
+                sampleType: sampleType,
+                predicate: nil,
+                limit: 1000,
+                sortDescriptors: nil
+            ) { _, results, _ in
+                promise(.success((results as? [HKQuantitySample]) ?? []))
+            }
+            self.healthKitStore.execute(query)
+        }
+    }
+
     /// Try to load samples from Health store with id and do some work
     /// Try to load samples from Health store with id and do some work
     private func loadSamplesFromHealth(
     private func loadSamplesFromHealth(
         sampleType: HKQuantityType,
         sampleType: HKQuantityType,
@@ -243,14 +405,14 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
                 if let bgSamples = addedObjects as? [HKQuantitySample],
                 if let bgSamples = addedObjects as? [HKQuantitySample],
                    bgSamples.isNotEmpty
                    bgSamples.isNotEmpty
                 {
                 {
-                    self.prepareSamplesToPublisherFetch(bgSamples)
+                    self.prepareBGSamplesToPublisherFetch(bgSamples)
                 }
                 }
             }
             }
         }
         }
         return query
         return query
     }
     }
 
 
-    private func prepareSamplesToPublisherFetch(_ samples: [HKQuantitySample]) {
+    private func prepareBGSamplesToPublisherFetch(_ samples: [HKQuantitySample]) {
         dispatchPrecondition(condition: .onQueue(processQueue))
         dispatchPrecondition(condition: .onQueue(processQueue))
         debug(.service, "Start preparing samples: \(String(describing: samples))")
         debug(.service, "Start preparing samples: \(String(describing: samples))")
 
 
@@ -333,7 +495,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
         fetch(nil)
         fetch(nil)
     }
     }
 
 
-    func deleteGlucise(syncID: String) {
+    func deleteGlucose(syncID: String) {
         guard settingsManager.settings.useAppleHealth,
         guard settingsManager.settings.useAppleHealth,
               let sampleType = Config.healthBGObject,
               let sampleType = Config.healthBGObject,
               checkAvailabilitySave(objectTypeToHealthStore: sampleType)
               checkAvailabilitySave(objectTypeToHealthStore: sampleType)
@@ -352,6 +514,54 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
             }
             }
         }
         }
     }
     }
+
+    // - MARK Carbs function
+
+    func deleteCarbs(syncID: String) {
+        guard settingsManager.settings.useAppleHealth,
+              let sampleType = Config.healthCarbObject,
+              checkAvailabilitySave(objectTypeToHealthStore: sampleType)
+        else { return }
+
+        processQueue.async {
+            let predicate = HKQuery.predicateForObjects(
+                withMetadataKey: HKMetadataKeySyncIdentifier,
+                operatorType: .equalTo,
+                value: syncID
+            )
+
+            self.healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in
+                guard let error = error else { return }
+                warning(.service, "Cannot delete sample with syncID: \(syncID)", error: error)
+            }
+        }
+    }
+
+    func carbsDidUpdate(_ carbs: [CarbsEntry]) {
+        saveIfNeeded(carbs: carbs)
+    }
+
+    // - MARK Insulin function
+
+    func deleteInsulin(syncID: String) {
+        guard settingsManager.settings.useAppleHealth,
+              let sampleType = Config.healthInsulinObject,
+              checkAvailabilitySave(objectTypeToHealthStore: sampleType)
+        else { return }
+
+        processQueue.async {
+            let predicate = HKQuery.predicateForObjects(
+                withMetadataKey: HKMetadataKeySyncIdentifier,
+                operatorType: .equalTo,
+                value: syncID
+            )
+
+            self.healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in
+                guard let error = error else { return }
+                warning(.service, "Cannot delete sample with syncID: \(syncID)", error: error)
+            }
+        }
+    }
 }
 }
 
 
 enum HealthKitPermissionRequestStatus {
 enum HealthKitPermissionRequestStatus {
@@ -365,3 +575,16 @@ enum HKError: Error {
     // Some data can be not available on current iOS-device
     // Some data can be not available on current iOS-device
     case dataNotAvailable
     case dataNotAvailable
 }
 }
+
+private struct InsulinBolus {
+    var id: String
+    var amount: Decimal
+    var date: Date
+}
+
+private struct InsulinBasal {
+    var id: String
+    var amount: Decimal
+    var startDelivery: Date
+    var endDelivery: Date
+}

+ 1 - 1
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -220,7 +220,7 @@ extension BaseWatchManager: WCSessionDelegate {
 
 
         if let carbs = message["carbs"] as? Double, carbs > 0 {
         if let carbs = message["carbs"] as? Double, carbs > 0 {
             carbsStorage.storeCarbs([
             carbsStorage.storeCarbs([
-                CarbsEntry(createdAt: Date(), carbs: Decimal(carbs), enteredBy: CarbsEntry.manual)
+                CarbsEntry(id: UUID().uuidString, createdAt: Date(), carbs: Decimal(carbs), enteredBy: CarbsEntry.manual)
             ])
             ])
 
 
             if settingsManager.settings.skipBolusScreenAfterCarbs {
             if settingsManager.settings.skipBolusScreenAfterCarbs {

+ 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.