소스 검색

Release 2.2.3 (#189)

**New Features**
*Add Fat and Protein from Watch app.
* Add meal notes (Food Type). Upload foodType to Nightscout.
* List all 3 upload toggles to Nightscout in Nightscout Config View instead (Feature request https://github.com/Artificial-Pancreas/iAPS/issues/140).
 * Add setting to Autotune which allows for autotuning of only basal rates
* Add dynamic ratio and dynamic ISF to the insulin sensitivity config view

**Bug fixes**
* Typo in logs
* glucose conversion bug (issue 141) fix by @Eugene Bashmakov
* Fix Fetching carbs from NS and prepare for fetching fat and protein from Nightscout.
* Fix minus button for nutrients in Watch app. Now easier to tap. Scale down text when necessary.
* Display current oref0 threshold when bolus alert about dropping below threshold.
* Prevent displaying button shape for bottom panel buttons
* Fix for Remote NS Announcements (bolus, open loop etc), by @Daniela C. Sort by timestamp before fetching to   make sure that always the last announcement is fetched. 
* Typo in stat view laying TIR chart.

**Miscellaneous**
* Change back to TDD in enacted pop-up
* TIR. Display value even when 0 % (<3.3, 3.9-7-8, > 11), by request
* Enable Apple Health integration footer update, by @mikeplante
* Logging of COBpredBG
* Display units in statistics.json and
fix for issue nr 156: https://github.com/Artificial-Pancreas/iAPS/issues/156
* Allow carb ratios as low as 1 g/U, by request
* ISSUE-161 | Rename title Target Ranges to Target Glucose by @Eugene Bashmakov
* Avoid rounding twice
* Use same sequence of nutrients as the Phone app.
* Crowdin updates and new localizations.
* Fix for issue157 https://github.com/Artificial-Pancreas/iAPS/issues/157
When there is a red bolus warning use the old bolus recommendation for Watch app. To avoid displaying alerts on watch app and to avoid overdose insulin when using iAPS watch app. 
* Raise max COBpredBG from 401 mg/dl to 1500 mg/dl. Test. This will indirectly also raise the max Eventual Glucose to 1500 mg/dl.
* Upload euglycemic range to optional statistics.json (70-140). Reduced code (made it more similar to the functions used in StatView).
Jon B Mårtensson 2 년 전
부모
커밋
dfa4476ebc
97개의 변경된 파일1757개의 추가작업 그리고 1238개의 파일을 삭제
  1. 1 1
      Config.xcconfig
  2. 11 1
      Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents
  3. 1 1
      Dependencies/CGMBLEKit/CGMBLEKit/nl.lproj/Localizable.strings
  4. 1 1
      Dependencies/CGMBLEKit/CGMBLEKitUI/nl.lproj/TransmitterManagerSetup.strings
  5. 1 1
      Dependencies/G7SensorKit/nl.lproj/Localizable.strings
  6. 1 1
      Dependencies/G7SensorKit/uk.lproj/Localizable.strings
  7. 1 1
      Dependencies/MinimedKit/MinimedKit/Resources/uk.lproj/Localizable.strings
  8. 2 2
      Dependencies/MinimedKit/MinimedKitUI/Resources/nl.lproj/Localizable.strings
  9. 2 2
      Dependencies/MinimedKit/MinimedKitUI/Resources/uk.lproj/Localizable.strings
  10. 22 22
      Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings
  11. 112 112
      Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings
  12. 30 30
      Dependencies/OmniKit/OmniKit/Resources/nl.lproj/Localizable.strings
  13. 25 25
      Dependencies/OmniKit/OmniKit/Resources/uk.lproj/Localizable.strings
  14. 10 10
      Dependencies/OmniKit/OmniKitUI/Resources/nl.lproj/Localizable.strings
  15. 25 25
      Dependencies/OmniKit/OmniKitUI/Resources/uk.lproj/Localizable.strings
  16. 2 2
      Dependencies/rileylink_ios/RileyLinkKitUI/uk.lproj/Localizable.strings
  17. 4 0
      FreeAPS.xcodeproj/project.pbxproj
  18. 34 0
      FreeAPS/Resources/Assets.xcassets/Colors/darkGray.colorset/Contents.json
  19. 1 1
      FreeAPS/Resources/javascript/bundle/autotune-core.js
  20. 1 1
      FreeAPS/Resources/javascript/bundle/determine-basal.js
  21. 1 1
      FreeAPS/Resources/javascript/bundle/profile.js
  22. 2 2
      FreeAPS/Resources/javascript/prepare/autotune-prep.js
  23. 0 6
      FreeAPS/Resources/javascript/prepare/determine-basal.js
  24. 1 1
      FreeAPS/Resources/javascript/prepare/meal.js
  25. 10 3
      FreeAPS/Resources/javascript/prepare/profile.js
  26. 4 4
      FreeAPS/Resources/uk.lproj/InfoPlist.strings
  27. 251 361
      FreeAPS/Sources/APS/APSManager.swift
  28. 3 1
      FreeAPS/Sources/APS/FetchAnnouncementsManager.swift
  29. 9 4
      FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift
  30. 86 12
      FreeAPS/Sources/APS/Storage/CarbsStorage.swift
  31. 2 0
      FreeAPS/Sources/APS/Storage/GlucoseStorage.swift
  32. 12 0
      FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift
  33. 1 0
      FreeAPS/Sources/Helpers/Color+Extensions.swift
  34. 21 7
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  35. 2 4
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  36. 21 8
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  37. 21 7
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  38. 16 7
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  39. 21 7
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  40. 21 7
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  41. 21 7
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  42. 21 7
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  43. 22 8
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  44. 22 8
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  45. 44 30
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  46. 21 7
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  47. 21 7
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  48. 21 7
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  49. 21 7
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  50. 21 7
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  51. 23 9
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  52. 21 7
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  53. 216 202
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  54. 21 7
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  55. 6 0
      FreeAPS/Sources/Models/CarbsEntry.swift
  56. 10 0
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  57. 15 0
      FreeAPS/Sources/Models/Loops.swift
  58. 6 0
      FreeAPS/Sources/Models/NightscoutTreatment.swift
  59. 9 0
      FreeAPS/Sources/Models/Statistics.swift
  60. 2 0
      FreeAPS/Sources/Models/Suggestion.swift
  61. 13 73
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift
  62. 13 4
      FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift
  63. 2 0
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigStateModel.swift
  64. 20 15
      FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift
  65. 1 2
      FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift
  66. 1 1
      FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift
  67. 0 13
      FreeAPS/Sources/Modules/CGM/CGMStateModel.swift
  68. 0 4
      FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift
  69. 1 1
      FreeAPS/Sources/Modules/CREditor/CREditorStateModel.swift
  70. 1 1
      FreeAPS/Sources/Modules/CREditor/View/CREditorRootView.swift
  71. 4 1
      FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift
  72. 5 3
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift
  73. 4 0
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  74. 1 1
      FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift
  75. 16 6
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  76. 1 0
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorDataFlow.swift
  77. 4 0
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorProvider.swift
  78. 21 4
      FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  79. 18 2
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift
  80. 7 1
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift
  81. 5 4
      FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift
  82. 1 1
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift
  83. 1 1
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  84. 18 34
      FreeAPS/Sources/Modules/Stat/View/ChartsView.swift
  85. 0 2
      FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift
  86. 0 1
      FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift
  87. 1 1
      FreeAPS/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift
  88. 3 0
      FreeAPS/Sources/Modules/WatchConfig/View/WatchConfigRootView.swift
  89. 2 0
      FreeAPS/Sources/Modules/WatchConfig/WatchConfigStateModel.swift
  90. 2 2
      FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift
  91. 2 0
      FreeAPS/Sources/Services/Network/NightscoutManager.swift
  92. 16 8
      FreeAPS/Sources/Services/WatchManager/WatchManager.swift
  93. 1 1
      FreeAPS/Sources/Views/TagCloudView.swift
  94. 1 0
      FreeAPSWatch WatchKit Extension/DataFlow.swift
  95. 208 45
      FreeAPSWatch WatchKit Extension/Views/CarbsView.swift
  96. 4 3
      FreeAPSWatch WatchKit Extension/WatchStateModel.swift
  97. 3 2
      README.md

+ 1 - 1
Config.xcconfig

@@ -1,5 +1,5 @@
 APP_DISPLAY_NAME = iAPS
-APP_VERSION = 2.2.2
+APP_VERSION = 2.2.3
 APP_BUILD_NUMBER = 1
 COPYRIGHT_NOTICE =
 DEVELOPER_TEAM = ##TEAM_ID##

+ 11 - 1
Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22158.8" systemVersion="22F66" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22F82" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
     <entity name="BGaverages" representedClassName="BGaverages" syncable="YES" codeGenerationType="class">
         <attribute name="average" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="average_1" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
@@ -19,6 +19,11 @@
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="enteredBy" optional="YES" attributeType="String"/>
     </entity>
+    <entity name="Fat" representedClassName="Fat" syncable="YES" codeGenerationType="class">
+        <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="enteredBy" optional="YES" attributeType="String"/>
+        <attribute name="fat" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
+    </entity>
     <entity name="HbA1c" representedClassName="HbA1c" syncable="YES" codeGenerationType="class">
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="hba1c" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
@@ -89,6 +94,11 @@
         <attribute name="fat" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="protein" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
     </entity>
+    <entity name="Protein" representedClassName="Protein" syncable="YES" codeGenerationType="class">
+        <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="enteredBy" optional="YES" attributeType="String"/>
+        <attribute name="protein" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
+    </entity>
     <entity name="Readings" representedClassName="Readings" syncable="YES" codeGenerationType="class">
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="glucose" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>

+ 1 - 1
Dependencies/CGMBLEKit/CGMBLEKit/nl.lproj/Localizable.strings

@@ -14,7 +14,7 @@
 "OK" = "Ok";
 
 /* invlid config error description */
-"Peripheral command was invalid" = "Perifere commando was ongeldig";
+"Peripheral command was invalid" = "Apparaat commando was ongeldig";
 
 /* Timeout error description */
 "Peripheral did not respond in time" = "Apparaat reageerde niet op tijd";

+ 1 - 1
Dependencies/CGMBLEKit/CGMBLEKitUI/nl.lproj/TransmitterManagerSetup.strings

@@ -2,7 +2,7 @@
 "5oU-vK-JHQ.text" = "Toegangsgegevens";
 
 /* Class = "UITableViewController"; title = "Transmitter Setup"; ObjectID = "Dds-49-o7G"; */
-"Dds-49-o7G.title" = "Zender Instellen";
+"Dds-49-o7G.title" = "Zender instellen";
 
 /* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */
 "GOT-KQ-cEh.text" = "Detail";

+ 1 - 1
Dependencies/G7SensorKit/nl.lproj/Localizable.strings

@@ -2,7 +2,7 @@
 "Dexcom G7" = "Dexcom G7";
 
 /* Descriptive text on G7StartupView */
-"iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management." = "iAPS kan G7 CGM-gegevens lezen, maar u moet nog steeds de Dexcom G7 App gebruiken voor koppeling, kalibratie en ander sensorbeheer.";
+"iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management." = "iAPS kan G7 CGM-gegevens lezen, maar je moet nog steeds de Dexcom G7 App gebruiken voor koppeling, kalibratie en ander sensorbeheer.";
 
 /* Button title for starting setup */
 "Continue" = "Vervolg";

+ 1 - 1
Dependencies/G7SensorKit/uk.lproj/Localizable.strings

@@ -2,7 +2,7 @@
 "Dexcom G7" = "Dexcom G7";
 
 /* Descriptive text on G7StartupView */
-"iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management." = "iAPS може читати дані G7 CGM, але ви все одно повинні використовувати додаток Dexcom G7 для парування, калібрування та іншого управління датчиком.";
+"iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management." = "iAPS може читати дані G7 CGM, але ви все одно повинні використовувати додаток Dexcom G7 для парування, калібрування та іншого управління сенсором.";
 
 /* Button title for starting setup */
 "Continue" = "Продовжити";

+ 1 - 1
Dependencies/MinimedKit/MinimedKit/Resources/uk.lproj/Localizable.strings

@@ -17,7 +17,7 @@
 "Bolus in progress" = "Подання болюса";
 
 /* Suggestions for diagnosing a command refused pump error */
-"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Переконайтеся, що помпа не зупинена або не перебуває в режимі заправки, а так само, що ВБС в помпі встановлено в U/год, а не у відсотках";
+"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Переконайтеся, що помпа не зупинена або не перебуває в режимі заправки, а так само, що ВБШ в помпі встановлено в U/год, а не у відсотках";
 
 /* Pump error code returned when command refused */
 "Command refused" = "Команду відхилено";

+ 2 - 2
Dependencies/MinimedKit/MinimedKitUI/Resources/nl.lproj/Localizable.strings

@@ -50,7 +50,7 @@
 "Changing time…" = "Tijd aanpassen...";
 
 /* Instructions on selecting battery chemistry type */
-"Choose the type of battery you are using in your pump for better alerting about low battery conditions." = "Kies het type batterij dat in de pomp wordt gebruikt, voor optimalisering van waarschuwing voor laag batterij niveau.";
+"Choose the type of battery you are using in your pump for better alerting about low battery conditions." = "Kies het type batterij dat je gebruikt in je pomp om beter te kunnen waarschuwen voor een te laag batterij niveau.";
 
 /* The title of the configuration section in MinimedPumpManager settings */
 "Configuration" = "Configuratie";
@@ -123,7 +123,7 @@
 "No, Keep Pump As Is" = "Nee, laat de pomp ongewijzigd";
 
 /* Pump find device instruction */
-"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "Ga op je pomp, naar het scherm 'Vind Apparaat' en selecteer \"Vind Apparaat\".\n\nHoofdmenu >\nHulpprogramma's >\nVerbind Apparaten >\nAndere Apparaten >\nAan >\nVind Apparaat";
+"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "Ga op je pomp naar het scherm 'Vind Apparaat' en selecteer \"Vind Apparaat\".\n\nHoofdmenu >\nHulpprogramma's >\nVerbind Apparaten >\nAndere Apparaten >\nAan >\nVind Apparaat";
 
 /* navigation title for pump battery type selection
    Text for medtronic pump preferred data source */

+ 2 - 2
Dependencies/MinimedKit/MinimedKitUI/Resources/uk.lproj/Localizable.strings

@@ -152,7 +152,7 @@
 "Region" = "Регіон";
 
 /* Title text for button to resume insulin delivery */
-"Resume Delivery" = "Відновити доставку";
+"Resume Delivery" = "Відновити подачу";
 
 /* Title text for button when insulin delivery is in the process of being resumed */
 "Resuming" = "Відновлюється";
@@ -188,7 +188,7 @@
 "Sync to Current Time" = "Встановити поточний час";
 
 /* Message for pod sync time action sheet */
-"The time on your pump is different from the current time. Do you want to update the time on your pump to the current time?" = "Час на вашому насосі відрізняється від поточного часу. Хочете оновити час на насосі до поточного?";
+"The time on your pump is different from the current time. Do you want to update the time on your pump to the current time?" = "Час на вашій помпіі відрізняється від поточного часу. Хочете оновити час на помпі до поточного?";
 
 /* Title for pod sync time action sheet. */
 "Time Change Detected" = "Виявлено зміну часу";

+ 22 - 22
Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings

@@ -9,7 +9,7 @@
 "Multiple Command Alert" = "Waarschuwing voor meerdere commando's";
 
 /* Alert content title for userPodExpiration pod alert */
-"Pod Expiration Reminder" = "Herinnering over de vervaltijd van uw Pod";
+"Pod Expiration Reminder" = "Waarschuwing dat je Pod vervalt";
 
 /* Alert content title for podExpiring pod alert */
 "Pod Expired" = "Pod verlopen";
@@ -48,7 +48,7 @@
 "Suspend In Progress Reminder" = "Onderbreek in voortgang herinnering";
 
 /* Alert content body for suspendEnded pod alert */
-"The insulin suspension period has ended.\n\nYou can resume delivery from the banner on the home screen or from your pump settings screen. You will be reminded again in 15 minutes." = "De insuline opschortingsperiode is afgelopen.\n\nU kunt de toediening hervatten via de banner op het beginscherm of via de pompinstellingen. U wordt na 15 minuten opnieuw herinnerd.";
+"The insulin suspension period has ended.\n\nYou can resume delivery from the banner on the home screen or from your pump settings screen. You will be reminded again in 15 minutes." = "De insuline opschortingsperiode is afgelopen.\n\nJe kunt de toediening hervatten via de banner op het beginscherm of via de pompinstellingen. Je wordt na 15 minuten opnieuw herinnerd.";
 
 /* Alert content body for finishSetupReminder pod alert */
 "Please finish pairing your pod." = "Voltooi alstublieft de koppeling van je pod.";
@@ -57,7 +57,7 @@
 "The time on your pump is different from the current time. You can review the pump time and and sync to current time in settings." = "De pomptijd verschilt van de huidige tijd. Je kunt de pomptijd bekijken en synchroniseren met de huidige tijd in instellingen.";
 
 /* Alert notification body for suspendEnded pod alert user notification */
-"Suspension time is up. Open the app and resume." = "Opschorting tijd is voorbij. Open de app en hervat.";
+"Suspension time is up. Open the app and resume." = "Opschorting tijd is voorbij. Open iAPS en hervat.";
 
 /* Action button default text for PodAlerts */
 "Ok" = "OK";
@@ -394,10 +394,10 @@
 "Failed to Cancel Manual Basal" = "Annuleren van handmatige basaal mislukt";
 
 /* */
-"Please deactivate the pod. When deactivation is complete, you may remove it and pair a new pod." = "Deactiveer de pod. Wanneer de deactivering voltooid is, kunt u hem verwijderen en een nieuwe pod koppelen.";
+"Please deactivate the pod. When deactivation is complete, you may remove it and pair a new pod." = "Deactiveer de pod. Wanneer de deactivering voltooid is, kun je hem verwijderen en een nieuwe Pod koppelen.";
 
 /* Instructions for deactivate pod when pod not on body */
-"Please deactivate the pod. When deactivation is complete, you may pair a new pod." = "Deactiveer de pod. Wanneer de deactivering voltooid is, kunt u hem verwijderen en een nieuwe pod koppelen.";
+"Please deactivate the pod. When deactivation is complete, you may pair a new pod." = "Deactiveer de Pod. Wanneer de deactivering voltooid is, kun je hem verwijderen en een nieuwe Pod koppelen.";
 
 /* Deactivate pod action button */
 "Deactivate Pod" = "Deactiveer Pod";
@@ -415,7 +415,7 @@
 "Continue" = "Vervolg";
 
 /* Format string for recovery suggestion during deactivate pod. */
-"There was a problem communicating with the pod. If this problem persists, tap Discard Pod. You can then activate a new Pod." = "Er was een probleem met de communicatie met de pod. Als dit probleem zich blijft voordoen, tik dan op 'weggooien'. U kunt dan een nieuwe Pod activeren.";
+"There was a problem communicating with the pod. If this problem persists, tap Discard Pod. You can then activate a new Pod." = "Er was een communicatieprobleem met de Pod. Als dit probleem zich blijft voordoen, tik dan op 'weggooien'. Je kunt dan een nieuwe Pod activeren.";
 
 /* Text for discard pod button */
 "Discard Pod" = "Pod verwijderen";
@@ -424,7 +424,7 @@
 "Remove Pod from Body" = "Verwijder Pod uit het lichaam";
 
 /* Alert message body for confirm pod attachment */
-"Your Pod may still be delivering Insulin.\nRemove it from your body, then tap “Continue.“" = "Mogelijk levert uw Pod nog steeds insuline op.\nVerwijder deze uit uw lichaam en tik op \"Doorgaan\"";
+"Your Pod may still be delivering Insulin.\nRemove it from your body, then tap “Continue.“" = "Mogelijk levert je Pod nog steeds insuline.\nVerwijder deze uit je lichaam en tik op \"Doorgaan\"";
 
 /* Insulin Unit */
 "U" = "E";
@@ -460,10 +460,10 @@
 "Prepare site." = "Bereid de plaats op je lichaam voor op de Pod.";
 
 /* Label text for step two of attach pod instructions */
-"Remove blue Pod needle cap and check cannula. Then remove paper backing." = "Verwijder de blauwe Pod naaldkap en controleer de canule. Verwijder vervolgens de papieren achterkant.";
+"Remove blue Pod needle cap and check cannula. Then remove paper backing." = "Verwijder de blauwe naalddop van de Pod en controleer de canule. Verwijder vervolgens de beschermlaag van de hechtstrip.";
 
 /* Label text for step three of attach pod instructions */
-"Check Pod, apply to site, then confirm pod attachment." = "Pod controleren, breng aan op de plek op je lichaam, dan pod bevestigen.";
+"Check Pod, apply to site, then confirm pod attachment." = "Pod controleren, breng aan op de plek op je lichaam, dan Pod bevestigen.";
 
 /* Action button title for attach pod view */
 "Continue" = "Vervolg";
@@ -475,7 +475,7 @@
 "Confirm Pod Attachment" = "Bevestig Pod plaatsing";
 
 /* Alert message body for confirm pod attachment */
-"Please confirm that the Pod is securely attached to your body.\n\nThe cannula can be inserted only once with each Pod. Tap “Confirm” when Pod is attached." = "Controleer of de Pod goed aan uw lichaam is bevestigd.\n\nDe canule kan slechts eenmaal per Pod worden ingebracht. Tik op \"Bevestigen\" wanneer de Pod is bevestigd.";
+"Please confirm that the Pod is securely attached to your body.\n\nThe cannula can be inserted only once with each Pod. Tap “Confirm” when Pod is attached." = "Controleer of de Pod goed aan je lichaam is bevestigd.\n\nDe canule kan slechts eenmaal per Pod worden ingebracht. Tik op \"Bevestigen\" wanneer de Pod goed is aangebracht op je lichaam.";
 
 /* Button title for confirm attachment option */
 "Confirm" = "Bevestig";
@@ -490,10 +490,10 @@
 "Inserted" = "Ingebracht";
 
 /* Check Cannula */
-"Check Cannula" = "Check cannule";
+"Check Cannula" = "Check canule";
 
 /* */
-"Is the cannula inserted properly?" = "Is de cannule correct ingebracht?";
+"Is the cannula inserted properly?" = "Is de canule correct ingebracht?";
 
 /* Description of proper cannula insertion */
 "The window on the top of the Pod should be colored pink when the cannula is properly inserted into the skin." = "Het venster aan de bovenkant van de Pod moet roze gekleurd zijn wanneer de canule goed in de huid is ingebracht.";
@@ -517,7 +517,7 @@
 "Deactivated" = "Uitgeschakeld";
 
 /* Format string for instructions for setup complete view. (1: app name) */
-"Your Pod is ready for use.\n\n%1$@ will remind you to change your pod before it expires. You can change this to a time convenient for you." = "Uw Pod is klaar voor gebruik.\n\n%1$@ zal u eraan herinneren om uw Pod te vervangen voordat deze verloopt. U kunt dit veranderen in een tijdstip dat u schikt.";
+"Your Pod is ready for use.\n\n%1$@ will remind you to change your pod before it expires. You can change this to a time convenient for you." = "Je Pod is klaar voor gebruik.\n\n%1$@ zal je eraan herinneren om je Pod te vervangen voordat deze verloopt. Je kunt dit veranderen in een tijdstip dat je beter uitkomt.";
 
 /* */
 "Scheduled Reminder" = "Geplande herinnering";
@@ -556,22 +556,22 @@
 "Omnipod Reminders" = "Omnipod herinneringen";
 
 /* Footer text for omnipod reminders section */
-"The app configures a reminder on the pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure when pairing a new Pod." = "De app configureert een herinnering op de Pod om u vooraf op de hoogte te stellen van het verstrijken van de Pod. Stel het aantal uren vooraf in dat u wilt instellen voor het koppelen van een nieuwe Pod.";
+"The app configures a reminder on the pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure when pairing a new Pod." = "iAPS configureert een herinnering op de Pod om je vooraf op de hoogte te stellen van het verlopen van de Pod. Stel het aantal uren vooraf in dat je wilt instellen voor het koppelen van een nieuwe Pod.";
 
 /* Footer text for scheduled reminder area */
-"This is a reminder that you scheduled when you paired your current Pod." = "Dit is een herinnering die u hebt gepland toen u uw huidige Pod koppelde.";
+"This is a reminder that you scheduled when you paired your current Pod." = "Dit is een herinnering die je hebt gepland toen je je huidige Pod koppelde.";
 
 /* */
 "Scheduled Reminder" = "Geplande herinnering";
 
 /* Footer text for low reservoir value row */
-"The App notifies you when the amount of insulin in the Pod reaches this level." = "De App meldt u wanneer de hoeveelheid insuline in de Pod dit niveau bereikt.";
+"The App notifies you when the amount of insulin in the Pod reaches this level." = "iAPS meldt u wanneer de hoeveelheid insuline in de Pod dit niveau bereikt.";
 
 /* Description text for critical alerts */
 "Critical Alerts" = "Kritieke waarschuwingen";
 
 /* Description text for critical alerts */
-"The reminders above will not sound if your device is in Silent or Do Not Disturb mode.\n\nThere are other critical Pod alerts and alarms that will sound even if your device is set to Silent or Do Not Disturb mode." = "De bovenstaande meldingen waarschuwen zonder geluid als uw apparaat in de modus Stil of Niet storen staat.\n\nEr zijn andere belangrijke Pod waarschuwingen en -alarmen die wel klinken, zelfs als uw apparaat in de modus Stil of Niet storen staat.";
+"The reminders above will not sound if your device is in Silent or Do Not Disturb mode.\n\nThere are other critical Pod alerts and alarms that will sound even if your device is set to Silent or Do Not Disturb mode." = "De bovenstaande meldingen waarschuwen zonder geluid als je apparaat in de modus Stil of Niet storen staat.\n\nEr zijn andere belangrijke Pod waarschuwingen en -alarmen die wel klinken, zelfs als je apparaat in de modus Stil of Niet storen staat.";
 /* navigation title for notification settings */
 "Notification Settings" = "Instellingen voor meldingen";
 
@@ -637,7 +637,7 @@
 "Missing Config" = "Configuratie ontbreekt";
 
 /* Alert format string for missing temp basal configuration. */
-"This PumpManager has not been configured with a maximum basal rate because it was added before manual temp basal was a feature. Please go to therapy settings -> delivery limits and set a new maximum basal rate." = "Deze Pompmanager is niet geconfigureerd met een maximale basaalstand omdat het is toegevoegd voordat handmatig tijdelijke basaal een functie was. Ga naar therapie instellingen -> afleverlimieten en stel een nieuwe maximale basaalstand in.";
+"This PumpManager has not been configured with a maximum basal rate because it was added before manual temp basal was a feature. Please go to therapy settings -> delivery limits and set a new maximum basal rate." = "Deze pompmanager is niet geconfigureerd met een maximale basaalstand omdat het is toegevoegd voordat handmatig tijdelijke basaal een functie was. Ga naar therapie instellingen -> afleverlimieten en stel een nieuwe maximale basaalstand in.";
 
 /* Label text for expiration reminder default row */
 "Expiration Reminder Default" = "Herinnering vervaldatum ingeschakeld";
@@ -697,16 +697,16 @@
 "Skip Omnipod Onboarding?" = "Omnipod onboarding overslaan?";
 
 /* Description text on ExpirationReminderSetupView */
-"The App notifies you in advance of Pod expiration.\n\nScroll to set the number of hours advance notice you would like to have." = "De app informeert u voordat de Pod vervalt.\n\nScroll om het aantal uren vooraf kennisgeving aan te geven dat u wilt hebben.";
+"The App notifies you in advance of Pod expiration.\n\nScroll to set the number of hours advance notice you would like to have." = "iAPS informeert je voordat de Pod vervalt.\n\nScroll om het aantal uren vooraf kennisgeving aan te geven dat je wilt hebben.";
 
 /* Text of continue button on ExpirationReminderSetupView" */
 "Next" = "Volgende";
 
 /* */
-"Expiration Reminder" = "Herinnering over de vervaltijd";
+"Expiration Reminder" = "Let op je Pod vervalt";
 
 /* Description text on LowReservoirReminderSetupView */
-"The App notifies you when the amount of insulin in the Pod reaches this level (50-10 U).\n\nScroll to set the number of units at which you would like to be reminded." = "De app laat je weten wanneer de hoeveelheid insuline in de Pod dit niveau bereikt (50-10 E).\n\nScroll om het aantal eenheden in te stellen waarop u wilt worden herinnerd.";
+"The App notifies you when the amount of insulin in the Pod reaches this level (50-10 U).\n\nScroll to set the number of units at which you would like to be reminded." = "iAPS laat je weten wanneer de hoeveelheid insuline in de Pod dit niveau bereikt (50-10 E).\n\nScroll om het aantal eenheden in te stellen waarop u wilt worden herinnerd.";
 
 /* Label text for low reservoir value row */
 "Low Reservoir" = "Laag reservoir niveau";
@@ -754,7 +754,7 @@
 "Try again" = "Probeer opnieuw";
 
 /* Recovery suggestion for PodCommsError.tooManyPodsFound */
-"Move to a new area away from any other pods and try again." = "Verplaats naar een nieuw gebied dat uit de buurt is van andere pods en probeer opnieuw.";
+"Move to a new area away from any other pods and try again." = "Verplaats naar een nieuw gebied dat uit de buurt is van andere Pods en probeer opnieuw.";
 
 /* Recovery suggestion for PodCommsError.noPodsFound */
 "Make sure your pod is filled and nearby." = "Zorg ervoor dat je pod is ingevuld en in de buurt is.";

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 112 - 112
Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings


+ 30 - 30
Dependencies/OmniKit/OmniKit/Resources/nl.lproj/Localizable.strings

@@ -42,7 +42,7 @@
 "Bolusing with temp basal" = "Bolussen met tijdelijk basaal";
 
 /* Pod state when inserting cannula */
-"Cannula inserting" = "Canule ingebracht";
+"Cannula inserting" = "Canule wordt geplaatst";
 
 /* String describing a dose that was certainly scheduled */
 "Certain" = "Zeker";
@@ -63,13 +63,13 @@
 "Communication issue: Unacknowledged command pending." = "Communicatieprobleem: niet-bevestigde opdracht in behandeling";
 
 /* Description for BeepPreference.manualCommands */
-"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When Loop automatically adjusts delivery, no confidence reminders are used." = "Piepjes uit de Pod zullen klinken voor commando's die u hebt geïnitieerd, zoals bolus, annulering, geschorst, hervatten, opslaan van meldingen etc. Als iAPS automatisch de levering wijzigt, worden er geen piepjes gebruikt.";
+"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When Loop automatically adjusts delivery, no confidence reminders are used." = "Piepjes uit de Pod zullen klinken voor commando's die je hebt geïnitieerd, zoals bolus, annulering, geschorst, hervatten, opslaan van meldingen etc. Als iAPS automatisch de levering wijzigt, worden er geen piepjes gebruikt.";
 
 /* Description for BeepPreference.extended */
-"Confidence reminders will sound when Loop automatically adjusts delivery as well as for commands you initiate." = "Piepjes zullen klinken als iAPS de levering automatisch aanpast evenals voor commando's die u initieert.";
+"Confidence reminders will sound when Loop automatically adjusts delivery as well as for commands you initiate." = "Piepjes zullen klinken als iAPS de levering automatisch aanpast evenals voor commando's die je initieert.";
 
 /* The title for AlarmCode.other notification */
-"Critical Pod Error" = "Kritieke Podfout";
+"Critical Pod Error" = "Kritieke Pod fout";
 
 /* Recovery suggestion when unexpected address received */
 "Crosstalk possible. Please move to a new location" = "Ruis mogelijk. Verplaats alsjeblieft naar een andere locatie";
@@ -132,13 +132,13 @@
 "Insulin type not configured" = "Insulinetype niet ingesteld";
 
 /* The format string for Internal pod fault (1: The fault code value) */
-"Internal pod fault %1$03d" = "Interne podfout %1$03d";
+"Internal pod fault %1$03d" = "Interne Pod fout %1$03d";
 
 /* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */
-"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "OnderbrokenBolus: %1$@ E (%2$@ E gepland) %3$@ %4$@ %5$@";
+"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "Onderbroken bolus: %1$@ E (%2$@ E gepland) %3$@ %4$@ %5$@";
 
 /* Error message for when unexpected address is received (1: received address) (2: expected address) */
-"Invalid address 0x%x. Expected 0x%x" = "Ongeldig adres 0x%1$x. Verwachte 0x%2$x";
+"Invalid address 0x%x. Expected 0x%x" = "Ongeldig adres 0x%1$x. Verwachtte 0x%2$x";
 
 /* Description for MessageError invalidAddress */
 "Invalid address: (%1$@)" = "Ongeldig adres: (%1$@)";
@@ -150,7 +150,7 @@
 "Invalid Setting" = "Ongeldige Instelling";
 
 /* Alert content title for lowReservoir pod alert */
-"Low Reservoir" = "Reservoir Bijna Leeg";
+"Low Reservoir" = "Reservoir bijna leeg";
 
 /* Format string for description for low reservoir advisory (1: reminder units) */
 "Low reservoir advisory (%1$gU)" = "Reservoir bijna leeg advies (%1$gE)";
@@ -171,11 +171,11 @@
 "Memory initialized" = "Geheugen geïnitialiseerd";
 
 /* Recovery suggestion for PodCommsError.tooManyPodsFound */
-"Move to a new area away from any other pods and try again." = "Ga naar een andere plek, weg van andere pods, en probeer opnieuw.";
+"Move to a new area away from any other pods and try again." = "Ga naar een andere plek, weg van andere pods en probeer opnieuw.";
 
 /* Alert content body for multiCommand pod alert
    Alert content title for multiCommand pod alert */
-"Multiple Command Alert" = "Meerdere commando's melding";
+"Multiple Command Alert" = "Melding meerdere commando's";
 
 /* Pod alert state when no alerts are active */
 "No alerts" = "Geen waarschuwingen";
@@ -194,13 +194,13 @@
 "No Pod" = "Geen pod";
 
 /* Error message shown when no pod is paired */
-"No pod paired" = "Geen pod gekoppeld";
+"No pod paired" = "Geen Pod gekoppeld";
 
 /* Error message for PodCommsError.noPodsFound */
-"No pods found" = "Geen pods gevonden";
+"No pods found" = "Geen Pods gevonden";
 
 /* Error message shown when no response from pod was received */
-"No response from pod" = "Geen reactie van pod";
+"No response from pod" = "Geen reactie van Pod";
 
 /* Error message shown when no response from pod was received */
 "No RileyLink available" = "Geen RileyLink beschikbaar";
@@ -228,22 +228,22 @@
 "Parsing Error: %1$@ in (%2$@)" = "Parseerfout: %1$@ in (%2$@)";
 
 /* Recovery suggestion on unexpected pod change */
-"Please bring only original pod in range or deactivate original pod" = "Breng alleen de oude pod binnen bereik of deactiveer de originele pod";
+"Please bring only original pod in range or deactivate original pod" = "Breng alleen de oude Pod binnen bereik of deactiveer de originele Pod";
 
 /* Recovery suggestion when no response is received from pod */
-"Please bring your pod closer to the RileyLink and try again" = "Breng de pod dichter bij de RileyLink en probeer opnieuw";
+"Please bring your pod closer to the RileyLink and try again" = "Breng de Pod dichter bij de RileyLink en probeer opnieuw";
 
 /* Alert content body for finishSetupReminder pod alert */
-"Please finish pairing your pod." = "Voltooi het koppelen van je pod.";
+"Please finish pairing your pod." = "Voltooi het koppelen van je Pod.";
 
 /* Recover suggestion shown when no pod is paired */
-"Please pair a new pod" = "Koppel een nieuwe pod";
+"Please pair a new pod" = "Koppel een nieuwe Pod";
 
 /* Recovery suggestion when pairing signal strength is too high */
-"Please reposition the RileyLink further from the pod" = "Plaats de RileyLink verder van de pod";
+"Please reposition the RileyLink further from the pod" = "Plaats de RileyLink verder van de Pod";
 
 /* Recovery suggestion when pairing signal strength is too low */
-"Please reposition the RileyLink relative to the pod" = "Verplaats de RileyLink ten opzichte van de pod";
+"Please reposition the RileyLink relative to the pod" = "Verplaats de RileyLink ten opzichte van de Pod";
 
 /* Error message shown when user cannot pair because pod is already paired */
 "Pod already paired" = "Pod al gekoppeld";
@@ -252,7 +252,7 @@
 "Pod already primed" = "Pod is al voorbereid";
 
 /* Status highlight message for other alarm. */
-"Pod Error" = "Podfout";
+"Pod Error" = "Pod fout";
 
 /* Description for expiration advisory alarm */
 "Pod expiration advisory alarm" = "Pod vervaltijd advies alarm";
@@ -279,22 +279,22 @@
 "Pod is suspended" = "Pod is onderbroken";
 
 /* Status highlight message for occlusion alarm. */
-"Pod Occlusion" = "Podverstopping";
+"Pod Occlusion" = "Pod verstopping";
 
 /* Alert content title for finishSetupReminder pod alert */
-"Pod Pairing Incomplete" = "Koppeling pod onvolledig";
+"Pod Pairing Incomplete" = "Koppeling Pod onvolledig";
 
 /* Error message shown when pod sends ack instead of response */
 "Pod sent ack instead of response" = "Pod heeft een bevestiging gestuurd in plaats van een antwoord";
 
 /* Pod state when prime or cannula insertion has not completed in the time allotted */
-"Pod setup window expired" = "Insteltijd van de pod is verlopen";
+"Pod setup window expired" = "Insteltijd van de Pod is verlopen";
 
 /* Description for pod suspended reminder */
-"Pod suspended reminder" = "Herinnering onderbroken pod";
+"Pod suspended reminder" = "Herinnering onderbroken Pod";
 
 /* Format string for poor pod signal strength */
-"Poor signal strength" = "Zwak signaalsterkte";
+"Poor signal strength" = "Zwakke signaalsterkte";
 
 /* Delivery status when pod is priming
    Pod status when priming */
@@ -359,7 +359,7 @@
 "Suspended" = "Onderbroken";
 
 /* Alert notification body for suspendEnded pod alert user notification */
-"Suspension time is up. Open the app and resume." = "De onderbrekingsperiode is verstreken. Open de app en hervat.";
+"Suspension time is up. Open the app and resume." = "De onderbrekingsperiode is verstreken. Open iAPS en hervat.";
 
 /* Pod tank fill completed */
 "Tank fill completed" = "Opslag vullen compleet";
@@ -380,7 +380,7 @@
 "TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "Tijdelijk basaal: %1$@ E/uur %2$@ %3$@ %4$@ E %5$@";
 
 /* Alert content body for suspendEnded pod alert */
-"The insulin suspension period has ended.\n\nYou can resume delivery from the banner on the home screen or from your pump settings screen. You will be reminded again in 15 minutes." = "De insulineonderbrekingsperiode is verstreken.\n\nJe kunt de toediening hervatten op het startscherm of via je pompinstellingenscherm. Over 15 minuten wordt je er opnieuw aan herinnerd.";
+"The insulin suspension period has ended.\n\nYou can resume delivery from the banner on the home screen or from your pump settings screen. You will be reminded again in 15 minutes." = "De insulineonderbrekingsperiode is verstreken.\n\nJe kunt de toediening hervatten op het startscherm of via je pompinstellingenscherm. Over 15 minuten word je er opnieuw aan herinnerd.";
 
 /* Alert content body for timeOffsetChangeDetected pod alert */
 "The time on your pump is different from the current time. You can review the pump time and and sync to current time in settings." = "De tijd op je pomp is anders dan de huidige tijd. Je kunt de tijd op de pomp bekijken en synchroniseren met de huidige tijd in instellingen.";
@@ -404,13 +404,13 @@
 "Unexpected message sequence number" = "Onverwacht volgnummer van bericht";
 
 /* Format string for unexpected pod change */
-"Unexpected pod change" = "Onverwachte podwissel";
+"Unexpected pod change" = "Onverwachte Pod wissel";
 
 /* Error message shown when empty response from pod was received */
-"Unexpected response from pod" = "Onverwachte reactie van pod";
+"Unexpected response from pod" = "Onverwachte reactie van Pod";
 
 /* The format string for Unknown pod fault (1: The fault code value) */
-"Unknown pod fault %1$03d" = "Onbekende podfout %1$03d";
+"Unknown pod fault %1$03d" = "Onbekende Pod fout %1$03d";
 
 /* Format string for description of MessageError unknownValue. (1: value) (2: Type) */
 "Unknown Value (%1$@) for type %2$@" = "Onbekende waarde (%1$@) voor type %2$@";

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 25 - 25
Dependencies/OmniKit/OmniKit/Resources/uk.lproj/Localizable.strings


+ 10 - 10
Dependencies/OmniKit/OmniKitUI/Resources/nl.lproj/Localizable.strings

@@ -153,7 +153,7 @@
 "Confidence Reminders" = "Meldingen met piepjes vanuit de Pod";
 
 /* Help text for BeepPreferenceSelectionView */
-"Confidence reminders are beeps from the pod which can be used to acknowledge selected commands." = "Dit zijn meldingen die met piepjes uit de pod komen en kunnen worden gebruikt ter bevestiging van geselecteerde opdrachten.";
+"Confidence reminders are beeps from the pod which can be used to acknowledge selected commands." = "Dit zijn meldingen die met piepjes uit de Pod komen en kunnen worden gebruikt ter bevestiging van geselecteerde opdrachten.";
 
 /* The title of the configuration section in settings */
 "Configuration" = "Configuratie";
@@ -237,7 +237,7 @@
 "Error Suspending" = "Fout bij onderbreken";
 
 /* The title of the cell showing the pod expiration reminder date */
-"Expiration Reminder" = "Vervaldatumherinnering";
+"Expiration Reminder" = "Melding vervaldatum";
 
 /* Label text for expiration reminder default row */
 "Expiration Reminder Default" = "Standaard herinnering";
@@ -261,7 +261,7 @@
 "Failed to Suspend Insulin Delivery" = "Insulinetoediening onderbreken mislukt";
 
 /* Alert title for error when updating confidence reminder preference */
-"Failed to update confidence reminder preference." = "Bevestigingsherinneringvoorkeuren bijwerken mislukt.";
+"Failed to update confidence reminder preference." = "Voorkeuren voor bijwerken van herrineringsmeldingen is mislukt.";
 
 /* Alert title for error when updating expiration reminder */
 "Failed to Update Expiration Reminder" = "Vervaldatumherinnering bijwerken mislukt";
@@ -279,7 +279,7 @@
 "Finish deactivation" = "Voltooi deactivering";
 
 /* The title of the command to finish pod setup */
-"Finish pod setup" = "Rond podinstallatie af";
+"Finish pod setup" = "Rond Pod installatie af";
 
 /* Action button title to continue at Setup Complete */
 "Finish Setup" = "Voltooi setup";
@@ -547,7 +547,7 @@
 "Remove Pump" = "Verwijder Pomp";
 
 /* Label text for step two of attach pod instructions */
-"Remove the pod's needle cap and check cannula. Then remove paper backing." = "Verwijder de naalddop van de pod en controleer de canule. Verwijder vervolgens de beschermlaag van de hechtstrip.";
+"Remove the pod's needle cap and check cannula. Then remove paper backing." = "Verwijder de blauwe naalddop van de Pod en controleer de canule. Verwijder vervolgens de beschermlaag van de hechtstrip.";
 
 /* Label indicating pod replacement necessary
    The title of the command to replace pod */
@@ -654,7 +654,7 @@
 "Sync With Pod" = "Synchroniseer met pod";
 
 /* Label text for step one of insert cannula instructions */
-"Tap below to start cannula insertion." = "Tik hieronder om het inbrengen van de canule te starten.";
+"Tap below to start cannula insertion." = "Tik hieronder om het inbrengen van de cannule te starten.";
 
 /* Navigation Title for ManualTempBasalEntryView */
 "Temporary Basal" = "Tijdelijke basaal";
@@ -669,10 +669,10 @@
 "Testing Commands…" = "Commando’s aan het testen...";
 
 /* Footer text for omnipod reminders section */
-"The app configures a reminder on the pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure when pairing a new Pod." = "De app stelt een herinnering in op de pod om je van tevoren op de hoogte te stellen van het verlopen van de Pod. Stel het aantal uur in van de vooraankondiging die je wenst in te stellen bij het koppelen van een nieuwe Pod.";
+"The app configures a reminder on the pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure when pairing a new Pod." = "iAPS stelt een herinnering in op de pod om je van tevoren op de hoogte te stellen van het verlopen van de Pod. Stel het aantal uur in van de vooraankondiging die je wenst in te stellen bij het koppelen van een nieuwe Pod.";
 
 /* Description text on ExpirationReminderSetupView */
-"The App notifies you in advance of Pod expiration.\n\nScroll to set the number of hours advance notice you would like to have." = "De App kondigt van tevoren aan wanneer de Pod verloopt.\n\nScroll om het aantal uren in te stellen voor de gewenste vooraankondiging.";
+"The App notifies you in advance of Pod expiration.\n\nScroll to set the number of hours advance notice you would like to have." = "iAPS kondigt van tevoren aan wanneer de Pod verloopt.\n\nScroll om het aantal uren in te stellen voor de gewenste vooraankondiging.";
 
 /* Description text on LowReservoirReminderSetupView */
 "The App notifies you when the amount of insulin in the Pod reaches this level (50-10 U).\n\nScroll to set the number of units at which you would like to be reminded." = "iAPS geeft een melding wanneer de hoeveelheid insuline in de Pod dit niveau bereikt (50-10 E).\n\nScroll om het aantal eenheden in te stellen waarbij je herinnerd wilt worden.";
@@ -693,13 +693,13 @@
 "The window on the top of the Pod should be colored pink when the cannula is properly inserted into the skin." = "Het venster aan de bovenkant van de pod moet roze gekleurd zijn wanneer de canule correct in de huid is ingebracht.";
 
 /* Format string for recovery suggestion during deactivate pod. */
-"There was a problem communicating with the pod. If this problem persists, tap Discard Pod. You can then activate a new Pod." = "Er is een communicatieprobleem opgetreden met de pod. Als dit probleem zich blijft voordoen, tik op Pod Verwijderen. Je kunt dan een nieuwe Pod activeren.";
+"There was a problem communicating with the pod. If this problem persists, tap Discard Pod. You can then activate a new Pod." = "Er is een communicatieprobleem opgetreden met de pod. Als dit probleem zich blijft voordoen, tik op 'Pod verwijderen'. Je kunt dan een nieuwe Pod activeren.";
 
 /* Footer text for scheduled reminder area */
 "This is a reminder that you scheduled when you paired your current Pod." = "Dit is een herinnering die je hebt gepland toen je je huidige Pod koppelde.";
 
 /* Alert format string for missing temp basal configuration. */
-"This Pump has not been configured with a maximum basal rate because it was added before manual temp basal was a feature. Please go to Therapy Settings -> Delivery Limits and set a new Maximum Basal Rate." = "Deze Pomp is niet geconfigureerd met een maximale basaalsnelheid omdat deze toegevoegd is voordat het handmatig instellen van een tijdelijke basaalsnelheid een functie was. Ga naar Therapieinstellingen -> Toedieningslimieten en stel een nieuwe Maximale Basaalsnelheid in.";
+"This Pump has not been configured with a maximum basal rate because it was added before manual temp basal was a feature. Please go to Therapy Settings -> Delivery Limits and set a new Maximum Basal Rate." = "Deze pomp is niet geconfigureerd met een maximale basaalsnelheid omdat deze toegevoegd is voordat het handmatig instellen van een tijdelijke basaalsnelheid een functie was. Ga naar Therapieinstellingen -> Toedieningslimieten en stel een nieuwe Maximale Basaalsnelheid in.";
 
 /* Label for expiration reminder row
    Label for scheduled expiration reminder row

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 25 - 25
Dependencies/OmniKit/OmniKitUI/Resources/uk.lproj/Localizable.strings


+ 2 - 2
Dependencies/rileylink_ios/RileyLinkKitUI/uk.lproj/Localizable.strings

@@ -75,10 +75,10 @@
 
 /* Detail text when battery alert disabled.
    Text indicating LED Mode is off */
-"Off" = "Off";
+"Off" = "Вимкнути";
 
 /* Text indicating LED Mode is on */
-"On" = "On";
+"On" = "Увімкнути";
 
 /* RileyLink setup description */
 "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink дозволяє зв'язуватися з помпою через Bluetooth Low Energy.";

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -20,6 +20,7 @@
 		190EBCCB29FF13CB00BA767D /* StatConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190EBCCA29FF13CB00BA767D /* StatConfigRootView.swift */; };
 		1927C8E62744606D00347C69 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1927C8E82744606D00347C69 /* InfoPlist.strings */; };
 		1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* Oref2_variables.swift */; };
+		193F6CDD2A512C8F001240FD /* Loops.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193F6CDC2A512C8F001240FD /* Loops.swift */; };
 		1967DFBE29D052C200759F30 /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFBD29D052C200759F30 /* Icons.swift */; };
 		1967DFC029D053AC00759F30 /* IconSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFBF29D053AC00759F30 /* IconSelection.swift */; };
 		1967DFC229D053D300759F30 /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFC129D053D300759F30 /* IconImage.swift */; };
@@ -511,6 +512,7 @@
 		1927C8FB2744612600347C69 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		1927C8FE274489BA00347C69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		1935363F28496F7D001E0B16 /* Oref2_variables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Oref2_variables.swift; sourceTree = "<group>"; };
+		193F6CDC2A512C8F001240FD /* Loops.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loops.swift; sourceTree = "<group>"; };
 		1967DFBD29D052C200759F30 /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = "<group>"; };
 		1967DFBF29D053AC00759F30 /* IconSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelection.swift; sourceTree = "<group>"; };
 		1967DFC129D053D300759F30 /* IconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImage.swift; sourceTree = "<group>"; };
@@ -1600,6 +1602,7 @@
 				1967DFBD29D052C200759F30 /* Icons.swift */,
 				19D4E4EA29FC6A9F00351451 /* TIRforChart.swift */,
 				19A910352A24D6D700C8951B /* DateFilter.swift */,
+				193F6CDC2A512C8F001240FD /* Loops.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -2631,6 +2634,7 @@
 				38F37828261260DC009DB701 /* Color+Extensions.swift in Sources */,
 				3811DE3F25C9D4A100A708ED /* SettingsStateModel.swift in Sources */,
 				CE7CA3582A064E2F004BE681 /* ListStateView.swift in Sources */,
+				193F6CDD2A512C8F001240FD /* Loops.swift in Sources */,
 				38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */,
 				38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */,
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,

+ 34 - 0
FreeAPS/Resources/Assets.xcassets/Colors/darkGray.colorset/Contents.json

@@ -0,0 +1,34 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "extended-gray",
+        "components" : {
+          "alpha" : "1.000",
+          "white" : "0.145"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "extended-gray",
+        "components" : {
+          "alpha" : "1.000",
+          "white" : "0.145"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
FreeAPS/Resources/javascript/bundle/autotune-core.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
FreeAPS/Resources/javascript/bundle/determine-basal.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
FreeAPS/Resources/javascript/bundle/profile.js


+ 2 - 2
FreeAPS/Resources/javascript/prepare/autotune-prep.js

@@ -1,6 +1,6 @@
 function generate(pumphistory_data, profile_data, glucose_data, pumpprofile_data, carb_data = {} , categorize_uam_as_basal = false, tune_insulin_curve = false) {
-    if (typeof(profile_data.carb_ratio) === 'undefined' || profile_data.carb_ratio < 2) {
-        if (typeof(pumpprofile_data.carb_ratio) === 'undefined' || pumpprofile_data.carb_ratio < 2) {
+    if (typeof(profile_data.carb_ratio) === 'undefined' || profile_data.carb_ratio < 1) {
+        if (typeof(pumpprofile_data.carb_ratio) === 'undefined' || pumpprofile_data.carb_ratio < 1) {
             console.log('{ "carbs": 0, "mealCOB": 0, "reason": "carb_ratios ' + profile_data.carb_ratio + ' and ' + pumpprofile_data.carb_ratio + ' out of bounds" }');
             return console.error("Error: carb_ratios " + profile_data.carb_ratio + ' and ' + pumpprofile_data.carb_ratio + " out of bounds");
         } else {

+ 0 - 6
FreeAPS/Resources/javascript/prepare/determine-basal.js

@@ -37,12 +37,6 @@ function generate(iob, currenttemp, glucose, profile, autosens = null, meal = nu
     if (basalProfile) {
         basalprofile = basalProfile;
     }
-    /*
-    var tdd_ = {};
-    if (tdd) {
-        tdd_ = tdd;
-    }
-     */
     
     var oref2_variables_ = {};
     if (oref2_variables) {

+ 1 - 1
FreeAPS/Resources/javascript/prepare/meal.js

@@ -1,7 +1,7 @@
 //для monitor/meal.json параметры: monitor/pumphistory-24h-zoned.json settings/profile.json monitor/clock-zoned.json monitor/glucose.json settings/basal_profile.json monitor/carbhistory.json
 
 function generate(pumphistory_data, profile_data, clock_data, glucose_data, basalprofile_data, carbhistory = false) {
-    if (typeof(profile_data.carb_ratio) === 'undefined' || profile_data.carb_ratio < 3) {
+    if (typeof(profile_data.carb_ratio) === 'undefined' || profile_data.carb_ratio < 1) {
         return {"error":"Error: carb_ratio " + profile_data.carb_ratio + " out of bounds"};
     }
 

+ 10 - 3
FreeAPS/Resources/javascript/prepare/profile.js

@@ -1,7 +1,7 @@
 //для pumpprofile.json параметры: settings/settings.json settings/bg_targets.json settings/insulin_sensitivities.json settings/basal_profile.json preferences.json settings/carb_ratios.json settings/temptargets.json settings/model.json
 //для profile.json параметры: settings/settings.json settings/bg_targets.json settings/insulin_sensitivities.json settings/basal_profile.json preferences.json settings/carb_ratios.json settings/temptargets.json settings/model.json settings/autotune.json
 
-function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data, preferences_input = false, carbratio_input = false, temptargets_input = false, model_input = false, autotune_input = false) {
+function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data, preferences_input = false, carbratio_input = false, temptargets_input = false, model_input = false, autotune_input = false, freeaps_data) {
     if (bgtargets_data.units !== 'mg/dL') {
         if (bgtargets_data.units === 'mmol/L') {
             for (var i = 0, len = bgtargets_data.targets.length; i < len; i++) {
@@ -34,6 +34,11 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data
     if (temptargets_input) {
         temptargets_data = temptargets_input;
     }
+    
+    var freeaps = { };
+    if (freeaps_data) {
+        freeaps = freeaps_data;
+    }
 
     var model_data = { };
     if (model_input) {
@@ -81,8 +86,10 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data
 
     if (autotune_data) {
         if (autotune_data.basalprofile) { inputs.basals = autotune_data.basalprofile; }
-        if (autotune_data.isfProfile) { inputs.isf = autotune_data.isfProfile; }
-        if (autotune_data.carb_ratio) { inputs.carbratio.schedule[0].ratio = autotune_data.carb_ratio; }
+        if (!freeaps.onlyAutotuneBasals) {
+            if (autotune_data.isfProfile) { inputs.isf = autotune_data.isfProfile; }
+            if (autotune_data.carb_ratio) { inputs.carbratio.schedule[0].ratio = autotune_data.carb_ratio; }
+        }
     }
     return freeaps_profile(inputs);
 }

+ 4 - 4
FreeAPS/Resources/uk.lproj/InfoPlist.strings

@@ -1,14 +1,14 @@
 /* Privacy - NFC Scan Usage Description */
-"NFCReaderUsageDescription" = "NFC використовується для сканування датчиків Libre.";
+"NFCReaderUsageDescription" = "NFC використовується для сканування сенсорів Libre.";
 
 /* Privacy - Bluetooth Always Usage Description */
-"NSBluetoothAlwaysUsageDescription" = "Bluetooth використовується для обміну з інсуліновими помпами та безперервним монітором глюкози";
+"NSBluetoothAlwaysUsageDescription" = "Bluetooth використовується для обміну з інсуліновими помпами та безперервним моніторингом глюкози";
 
 /* Privacy - Bluetooth Peripheral Usage Description */
-"NSBluetoothPeripheralUsageDescription" = "Bluetooth використовується для обміну з інсуліновими помпами та безперервним монітором глюкози";
+"NSBluetoothPeripheralUsageDescription" = "Bluetooth використовується для обміну з інсуліновими помпами та безперервним моніторингом глюкози";
 
 /* Privacy - Face ID Usage Description */
-"NSFaceIDUsageDescription" = "Авторизуйтесь для доступу до болюсу";
+"NSFaceIDUsageDescription" = "Для авторизованого болюсу";
 
 /* Privacy - Calendars Usage Description */
 "NSCalendarsUsageDescription" = "Для створення нових подій глюкози використовується календар.";

+ 251 - 361
FreeAPS/Sources/APS/APSManager.swift

@@ -732,7 +732,7 @@ final class BaseAPSManager: APSManager, Injectable {
         return rounded
     }
 
-    private func medianCalculation(array: [Double]) -> Double {
+    private func medianCalculationDouble(array: [Double]) -> Double {
         guard !array.isEmpty else {
             return 0
         }
@@ -745,7 +745,138 @@ final class BaseAPSManager: APSManager, Injectable {
         return sorted[length / 2]
     }
 
-    // Add to statistics.JSON
+    private func medianCalculation(array: [Int]) -> Double {
+        guard !array.isEmpty else {
+            return 0
+        }
+        let sorted = array.sorted()
+        let length = array.count
+
+        if length % 2 == 0 {
+            return Double((sorted[length / 2 - 1] + sorted[length / 2]) / 2)
+        }
+        return Double(sorted[length / 2])
+    }
+
+    private func tir(_ array: [Readings]) -> (TIR: Double, hypos: Double, hypers: Double, normal_: Double) {
+        let glucose = array
+        let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) })
+        let totalReadings = justGlucoseArray.count
+        let highLimit = settingsManager.settings.high
+        let lowLimit = settingsManager.settings.low
+        let hyperArray = glucose.filter({ $0.glucose >= Int(highLimit) })
+        let hyperReadings = hyperArray.compactMap({ each in each.glucose as Int16 }).count
+        let hyperPercentage = Double(hyperReadings) / Double(totalReadings) * 100
+        let hypoArray = glucose.filter({ $0.glucose <= Int(lowLimit) })
+        let hypoReadings = hypoArray.compactMap({ each in each.glucose as Int16 }).count
+        let hypoPercentage = Double(hypoReadings) / Double(totalReadings) * 100
+        // Euglyccemic range
+        let normalArray = glucose.filter({ $0.glucose >= 70 && $0.glucose <= 140 })
+        let normalReadings = normalArray.compactMap({ each in each.glucose as Int16 }).count
+        let normalPercentage = Double(normalReadings) / Double(totalReadings) * 100
+        // TIR
+        let tir = 100 - (hypoPercentage + hyperPercentage)
+        return (
+            roundDouble(tir, 1),
+            roundDouble(hypoPercentage, 1),
+            roundDouble(hyperPercentage, 1),
+            roundDouble(normalPercentage, 1)
+        )
+    }
+
+    private func glucoseStats(_ fetchedGlucose: [Readings])
+        -> (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)
+    {
+        let glucose = fetchedGlucose
+        // First date
+        let last = glucose.last?.date ?? Date()
+        // Last date (recent)
+        let first = glucose.first?.date ?? Date()
+        // Total time in days
+        let numberOfDays = (first - last).timeInterval / 8.64E4
+        let denominator = numberOfDays < 1 ? 1 : numberOfDays
+        let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) })
+        let sumReadings = justGlucoseArray.reduce(0, +)
+        let countReadings = justGlucoseArray.count
+        let glucoseAverage = Double(sumReadings) / Double(countReadings)
+        let medianGlucose = medianCalculation(array: justGlucoseArray)
+        var NGSPa1CStatisticValue = 0.0
+        var IFCCa1CStatisticValue = 0.0
+
+        NGSPa1CStatisticValue = (glucoseAverage + 46.7) / 28.7 // NGSP (%)
+        IFCCa1CStatisticValue = 10.929 *
+            (NGSPa1CStatisticValue - 2.152) // IFCC (mmol/mol)  A1C(mmol/mol) = 10.929 * (A1C(%) - 2.15)
+        var sumOfSquares = 0.0
+
+        for array in justGlucoseArray {
+            sumOfSquares += pow(Double(array) - Double(glucoseAverage), 2)
+        }
+        var sd = 0.0
+        var cv = 0.0
+        // Avoid division by zero
+        if glucoseAverage > 0 {
+            sd = sqrt(sumOfSquares / Double(countReadings))
+            cv = sd / Double(glucoseAverage) * 100
+        }
+        let conversionFactor = 0.0555
+        let units = settingsManager.settings.units
+
+        var output: (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)
+        output = (
+            ifcc: IFCCa1CStatisticValue,
+            ngsp: NGSPa1CStatisticValue,
+            average: glucoseAverage * (units == .mmolL ? conversionFactor : 1),
+            median: medianGlucose * (units == .mmolL ? conversionFactor : 1),
+            sd: sd * (units == .mmolL ? conversionFactor : 1), cv: cv,
+            readings: Double(countReadings) / denominator
+        )
+        return output
+    }
+
+    private func loops(_ fetchedLoops: [LoopStatRecord]) -> Loops {
+        let loops = fetchedLoops
+        // First date
+        let previous = loops.last?.end ?? Date()
+        // Last date (recent)
+        let current = loops.first?.start ?? Date()
+        // Total time in days
+        let totalTime = (current - previous).timeInterval / 8.64E4
+        //
+        let durationArray = loops.compactMap({ each in each.duration })
+        let durationArrayCount = durationArray.count
+        let durationAverage = durationArray.reduce(0, +) / Double(durationArrayCount) * 60
+        let medianDuration = medianCalculationDouble(array: durationArray) * 60
+        let max_duration = (durationArray.max() ?? 0) * 60
+        let min_duration = (durationArray.min() ?? 0) * 60
+        let successsNR = loops.compactMap({ each in each.loopStatus }).filter({ each in each!.contains("Success") }).count
+        let errorNR = durationArrayCount - successsNR
+        let total = Double(successsNR + errorNR) == 0 ? 1 : Double(successsNR + errorNR)
+        let successRate: Double? = (Double(successsNR) / total) * 100
+        let loopNr = totalTime <= 1 ? total : round(total / (totalTime != 0 ? totalTime : 1))
+        let intervalArray = loops.compactMap({ each in each.interval as Double })
+        let count = intervalArray.count != 0 ? intervalArray.count : 1
+        let median_interval = medianCalculationDouble(array: intervalArray)
+        let intervalAverage = intervalArray.reduce(0, +) / Double(count)
+        let maximumInterval = intervalArray.max()
+        let minimumInterval = intervalArray.min()
+        //
+        let output = Loops(
+            loops: Int(loopNr),
+            errors: errorNR,
+            success_rate: roundDecimal(Decimal(successRate ?? 0), 1),
+            avg_interval: roundDecimal(Decimal(intervalAverage), 1),
+            median_interval: roundDecimal(Decimal(median_interval), 1),
+            min_interval: roundDecimal(Decimal(minimumInterval ?? 0), 1),
+            max_interval: roundDecimal(Decimal(maximumInterval ?? 0), 1),
+            avg_duration: roundDecimal(Decimal(durationAverage), 1),
+            median_duration: roundDecimal(Decimal(medianDuration), 1),
+            min_duration: roundDecimal(Decimal(min_duration), 1),
+            max_duration: roundDecimal(Decimal(max_duration), 1)
+        )
+        return output
+    }
+
+    // Add to statistics.JSON for upload to NS.
     private func statistics() {
         let now = Date()
         if settingsManager.settings.uploadStats {
@@ -766,22 +897,21 @@ final class BaseAPSManager: APSManager, Injectable {
                 let units = self.settingsManager.settings.units
                 let preferences = settingsManager.preferences
 
+                // Carbs
                 var carbs = [Carbohydrates]()
                 var carbTotal: Decimal = 0
                 let requestCarbs = Carbohydrates.fetchRequest() as NSFetchRequest<Carbohydrates>
                 let daysAgo = Date().addingTimeInterval(-1.days.timeInterval)
                 requestCarbs.predicate = NSPredicate(format: "carbs > 0 AND date > %@", daysAgo as NSDate)
-
                 let sortCarbs = NSSortDescriptor(key: "date", ascending: true)
                 requestCarbs.sortDescriptors = [sortCarbs]
                 try? carbs = coredataContext.fetch(requestCarbs)
-
                 carbTotal = carbs.map({ carbs in carbs.carbs as? Decimal ?? 0 }).reduce(0, +)
 
+                // TDD
                 var tdds = [TDD]()
                 var currentTDD: Decimal = 0
                 var tddTotalAverage: Decimal = 0
-
                 let requestTDD = TDD.fetchRequest() as NSFetchRequest<TDD>
                 let sort = NSSortDescriptor(key: "timestamp", ascending: false)
                 let daysOf14Ago = Date().addingTimeInterval(-14.days.timeInterval)
@@ -806,7 +936,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 } else if preferences.useNewFormula, !preferences.sigmoid,!preferences.enableDynamicCR {
                     algo_ = "Dynamic ISF: Logarithmic"
                 }
-
                 let af = preferences.adjustmentFactor
                 let insulin_type = preferences.curve
                 let buildDate = Bundle.main.buildDate
@@ -825,284 +954,79 @@ final class BaseAPSManager: APSManager, Injectable {
                 } else if preferences.curve.rawValue == "ultra-rapid" {
                     iPa = 50
                 }
-
-                var lsr = [LoopStatRecord]()
-
-                let requestLSR = LoopStatRecord.fetchRequest() as NSFetchRequest<LoopStatRecord>
-                requestLSR.predicate = NSPredicate(
-                    format: "interval > 0 AND start > %@",
-                    Date().addingTimeInterval(-24.hours.timeInterval) as NSDate
-                )
-                let sortLSR = NSSortDescriptor(key: "start", ascending: false)
-                requestLSR.sortDescriptors = [sortLSR]
-
-                try? lsr = coredataContext.fetch(requestLSR)
-                let loops = lsr
-
-                let durationArray = loops.compactMap({ each in each.duration })
-                let durationArrayCount = durationArray.count
-                let successsNR = loops.compactMap({ each in each.loopStatus }).filter({ each in each!.contains("Success") }).count
-
-                let durationAverage = durationArray.reduce(0, +) / Double(durationArrayCount)
-                let medianDuration = medianCalculation(array: durationArray)
-                let minimumDuration = durationArray.min() ?? 0
-                let maximumDuration = durationArray.max() ?? 0
-                let errorNR = durationArrayCount - successsNR
-                let successRate: Double? = (Double(successsNR) / Double(successsNR + errorNR)) * 100
-                let loopNr = successsNR + errorNR
-
-                let intervalArray = loops.compactMap({ each in each.interval })
-                let intervalArrayCount = intervalArray.count
-                let intervalAverage = intervalArray.reduce(0, +) / Double(intervalArrayCount)
-                let intervalMedian = medianCalculation(array: intervalArray)
-                let maximumInterval = intervalArray.max() ?? 0
-                let minimumInterval = intervalArray.min() ?? 0
-
-                var glucose = [Readings]()
-
-                var firstElementTime = Date()
-                var lastElementTime = Date()
-                var currentIndexTime = Date()
-
-                var bg: Decimal = 0
-
-                var bgArray: [Double] = []
-                var bgArray_1_: [Double] = []
-                var bgArray_7_: [Double] = []
-                var bgArray_30_: [Double] = []
-
-                var bgArrayForTIR: [(bg_: Double, date_: Date)] = []
-                var bgArray_1: [(bg_: Double, date_: Date)] = []
-                var bgArray_7: [(bg_: Double, date_: Date)] = []
-                var bgArray_30: [(bg_: Double, date_: Date)] = []
-
-                var medianBG = 0.0
-                var nr_bgs: Decimal = 0
-                var bg_1: Decimal = 0
-                var bg_7: Decimal = 0
-                var bg_30: Decimal = 0
-                var bg_total: Decimal = 0
-                var j = -1
-                var conversionFactor: Decimal = 1
-                if units == .mmolL {
-                    conversionFactor = 0.0555
-                }
-
-                var numberOfDays: Double = 0
-                var nr1: Decimal = 0
-
+                // CGM Readings
+                var glucose_24 = [Readings]() // Day
+                var glucose_7 = [Readings]() // Week
+                var glucose_30 = [Readings]() // Month
+                var glucose = [Readings]() // Total
+                let filter = DateFilter()
+                // 24h
+                let requestGFS_24 = Readings.fetchRequest() as NSFetchRequest<Readings>
+                let sortGlucose_24 = NSSortDescriptor(key: "date", ascending: false)
+                requestGFS_24.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.day)
+                requestGFS_24.sortDescriptors = [sortGlucose_24]
+                try? glucose_24 = coredataContext.fetch(requestGFS_24)
+                // Week
+                let requestGFS_7 = Readings.fetchRequest() as NSFetchRequest<Readings>
+                let sortGlucose_7 = NSSortDescriptor(key: "date", ascending: false)
+                requestGFS_7.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.week)
+                requestGFS_7.sortDescriptors = [sortGlucose_7]
+                try? glucose_7 = coredataContext.fetch(requestGFS_7)
+                // Month
+                let requestGFS_30 = Readings.fetchRequest() as NSFetchRequest<Readings>
+                let sortGlucose_30 = NSSortDescriptor(key: "date", ascending: false)
+                requestGFS_30.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.month)
+                requestGFS_30.sortDescriptors = [sortGlucose_30]
+                try? glucose_30 = coredataContext.fetch(requestGFS_30)
+                // Total
                 let requestGFS = Readings.fetchRequest() as NSFetchRequest<Readings>
                 let sortGlucose = NSSortDescriptor(key: "date", ascending: false)
+                requestGFS.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.total)
                 requestGFS.sortDescriptors = [sortGlucose]
-
                 try? glucose = coredataContext.fetch(requestGFS)
 
-                // Time In Range (%) and Average Glucose. This will be refactored later after some testing.
-                let endIndex = glucose.count - 1
-
-                firstElementTime = glucose[0].date ?? Date()
-                lastElementTime = glucose[endIndex].date ?? Date()
-
-                currentIndexTime = firstElementTime
-
-                numberOfDays = (firstElementTime - lastElementTime).timeInterval / 8.64E4
-
-                // Make arrays for median calculations and calculate averages
-                if endIndex >= 0, (glucose.first?.glucose ?? 0) != 0 {
-                    repeat {
-                        j += 1
-                        if glucose[j].glucose > 0 {
-                            currentIndexTime = glucose[j].date ?? firstElementTime
-                            bg += Decimal(glucose[j].glucose) * conversionFactor
-                            bgArray.append(Double(glucose[j].glucose) * Double(conversionFactor))
-                            bgArrayForTIR.append((Double(glucose[j].glucose), glucose[j].date!))
-                            nr_bgs += 1
-                            if (firstElementTime - currentIndexTime).timeInterval <= 8.64E4 { // 1 day
-                                bg_1 = bg / nr_bgs
-                                bgArray_1 = bgArrayForTIR
-                                bgArray_1_ = bgArray
-                                nr1 = nr_bgs
-                            }
-                            if (firstElementTime - currentIndexTime).timeInterval <= 6.048E5 { // 7 days
-                                bg_7 = bg / nr_bgs
-                                bgArray_7 = bgArrayForTIR
-                                bgArray_7_ = bgArray
-                            }
-                            if (firstElementTime - currentIndexTime).timeInterval <= 2.592E6 { // 30 days
-                                bg_30 = bg / nr_bgs
-                                bgArray_30 = bgArrayForTIR
-                                bgArray_30_ = bgArray
-                            }
-                        }
-                    } while j != glucose.count - 1
-                } else { return }
-
-                if nr_bgs > 0 {
-                    // Up to 91 days
-                    bg_total = bg / nr_bgs
-                }
-
-                // Total median
-                medianBG = medianCalculation(array: bgArray)
-
-                func tir(_ array: [(bg_: Double, date_: Date)]) -> (TIR: Double, hypos: Double, hypers: Double) {
-                    var timeInHypo = 0.0
-                    var timeInHyper = 0.0
-                    var hypos = 0.0
-                    var hypers = 0.0
-                    var i = -1
-                    var lastIndex = false
-                    let endIndex = array.count - 1
-
-                    let hypoLimit = settingsManager.settings.low
-                    let hyperLimit = settingsManager.settings.high
+                // First date
+                let previous = glucose.last?.date ?? Date()
+                // Last date (recent)
+                let current = glucose.first?.date ?? Date()
+                // Total time in days
+                let numberOfDays = (current - previous).timeInterval / 8.64E4
 
-                    var full_time = 0.0
-                    if endIndex > 0 {
-                        full_time = (array[0].date_ - array[endIndex].date_).timeInterval
-                    }
-                    while i < endIndex {
-                        i += 1
-                        let currentTime = array[i].date_
-                        var previousTime = currentTime
-                        if i + 1 <= endIndex {
-                            previousTime = array[i + 1].date_
-                        } else {
-                            lastIndex = true
-                        }
-                        if array[i].bg_ < Double(hypoLimit), !lastIndex {
-                            // Exclude duration between CGM readings which are more than 30 minutes
-                            timeInHypo += min((currentTime - previousTime).timeInterval, 30.minutes.timeInterval)
-                        } else if array[i].bg_ >= Double(hyperLimit), !lastIndex {
-                            timeInHyper += min((currentTime - previousTime).timeInterval, 30.minutes.timeInterval)
-                        }
-                    }
-                    if timeInHypo == 0.0 {
-                        hypos = 0
-                    } else if full_time != 0.0 { hypos = (timeInHypo / full_time) * 100
-                    }
-                    if timeInHyper == 0.0 {
-                        hypers = 0
-                    } else if full_time != 0.0 { hypers = (timeInHyper / full_time) * 100
-                    }
-                    let TIR = 100 - (hypos + hypers)
-                    return (roundDouble(TIR, 1), roundDouble(hypos, 1), roundDouble(hypers, 1))
-                }
-
-                // HbA1c estimation (%, mmol/mol) 1 day
-                var NGSPa1CStatisticValue: Decimal = 0.0
-                var IFCCa1CStatisticValue: Decimal = 0.0
-                if nr_bgs > 0 {
-                    NGSPa1CStatisticValue = ((bg_1 / conversionFactor) + 46.7) / 28.7 // NGSP (%)
-                    IFCCa1CStatisticValue = 10.929 *
-                        (NGSPa1CStatisticValue - 2.152) // IFCC (mmol/mol)  A1C(mmol/mol) = 10.929 * (A1C(%) - 2.15)
-                }
-                // 7 days
-                var NGSPa1CStatisticValue_7: Decimal = 0.0
-                var IFCCa1CStatisticValue_7: Decimal = 0.0
-                if nr_bgs > 0 {
-                    NGSPa1CStatisticValue_7 = ((bg_7 / conversionFactor) + 46.7) / 28.7
-                    IFCCa1CStatisticValue_7 = 10.929 * (NGSPa1CStatisticValue_7 - 2.152)
-                }
-                // 30 days
-                var NGSPa1CStatisticValue_30: Decimal = 0.0
-                var IFCCa1CStatisticValue_30: Decimal = 0.0
-                if nr_bgs > 0 {
-                    NGSPa1CStatisticValue_30 = ((bg_30 / conversionFactor) + 46.7) / 28.7
-                    IFCCa1CStatisticValue_30 = 10.929 * (NGSPa1CStatisticValue_30 - 2.152)
-                }
-                // Total days
-                var NGSPa1CStatisticValue_total: Decimal = 0.0
-                var IFCCa1CStatisticValue_total: Decimal = 0.0
-                if nr_bgs > 0 {
-                    NGSPa1CStatisticValue_total = ((bg_total / conversionFactor) + 46.7) / 28.7
-                    IFCCa1CStatisticValue_total = 10.929 *
-                        (NGSPa1CStatisticValue_total - 2.152)
-                }
+                // Get glucose computations for every case
+                let oneDayGlucose = glucoseStats(glucose_24)
+                let sevenDaysGlucose = glucoseStats(glucose_7)
+                let thirtyDaysGlucose = glucoseStats(glucose_30)
+                let totalDaysGlucose = glucoseStats(glucose)
 
                 let median = Durations(
-                    day: roundDecimal(Decimal(medianCalculation(array: bgArray_1_)), 1),
-                    week: roundDecimal(Decimal(medianCalculation(array: bgArray_7_)), 1),
-                    month: roundDecimal(Decimal(medianCalculation(array: bgArray_30_)), 1),
-                    total: roundDecimal(Decimal(medianBG), 1)
+                    day: roundDecimal(Decimal(oneDayGlucose.median), 1),
+                    week: roundDecimal(Decimal(sevenDaysGlucose.median), 1),
+                    month: roundDecimal(Decimal(thirtyDaysGlucose.median), 1),
+                    total: roundDecimal(Decimal(totalDaysGlucose.median), 1)
                 )
 
-                let saveMedianToCoreData = BGmedian(context: self.coredataContext)
-                saveMedianToCoreData.date = Date()
-                saveMedianToCoreData.median = median.total as NSDecimalNumber
-                saveMedianToCoreData.median_1 = median.day as NSDecimalNumber
-                saveMedianToCoreData.median_7 = median.week as NSDecimalNumber
-                saveMedianToCoreData.median_30 = median.month as NSDecimalNumber
-
-                try? self.coredataContext.save()
-
-                var hbs = Durations(
-                    day: roundDecimal(NGSPa1CStatisticValue, 1),
-                    week: roundDecimal(NGSPa1CStatisticValue_7, 1),
-                    month: roundDecimal(NGSPa1CStatisticValue_30, 1),
-                    total: roundDecimal(NGSPa1CStatisticValue_total, 1)
-                )
-
-                let saveHbA1c = HbA1c(context: self.coredataContext)
-                saveHbA1c.date = Date()
-                saveHbA1c.hba1c = NGSPa1CStatisticValue_total as NSDecimalNumber
-                saveHbA1c.hba1c_1 = NGSPa1CStatisticValue as NSDecimalNumber
-                saveHbA1c.hba1c_7 = NGSPa1CStatisticValue_7 as NSDecimalNumber
-                saveHbA1c.hba1c_30 = NGSPa1CStatisticValue_30 as NSDecimalNumber
-
-                try? self.coredataContext.save()
-
-                // Convert to user-preferred unit
                 let overrideHbA1cUnit = settingsManager.settings.overrideHbA1cUnit
-                if units == .mmolL {
-                    // Override if users sets overrideHbA1cUnit: true
-                    if !overrideHbA1cUnit {
-                        hbs = Durations(
-                            day: roundDecimal(IFCCa1CStatisticValue, 1),
-                            week: roundDecimal(IFCCa1CStatisticValue_7, 1),
-                            month: roundDecimal(IFCCa1CStatisticValue_30, 1),
-                            total: roundDecimal(IFCCa1CStatisticValue_total, 1)
-                        )
-                    }
-                } else if units != .mmolL, overrideHbA1cUnit {
-                    hbs = Durations(
-                        day: roundDecimal(IFCCa1CStatisticValue, 1),
-                        week: roundDecimal(IFCCa1CStatisticValue_7, 1),
-                        month: roundDecimal(IFCCa1CStatisticValue_30, 1),
-                        total: roundDecimal(IFCCa1CStatisticValue_total, 1)
-                    )
-                }
-
-                let nrOfCGMReadings = nr1
 
-                let loopstat = LoopCycles(
-                    loops: loopNr,
-                    errors: errorNR,
-                    readings: Int(nrOfCGMReadings),
-                    success_rate: Decimal(round(successRate ?? 0)),
-                    avg_interval: roundDecimal(Decimal(intervalAverage), 1),
-                    median_interval: roundDecimal(Decimal(intervalMedian), 1),
-                    min_interval: roundDecimal(Decimal(minimumInterval), 1),
-                    max_interval: roundDecimal(Decimal(maximumInterval), 1),
-                    avg_duration: Decimal(roundDouble(durationAverage, 2)),
-                    median_duration: Decimal(roundDouble(medianDuration, 2)),
-                    min_duration: roundDecimal(Decimal(minimumDuration), 2),
-                    max_duration: Decimal(roundDouble(maximumDuration, 1))
+                let hbs = Durations(
+                    day: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
+                        roundDecimal(Decimal(oneDayGlucose.ifcc), 1) : roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
+                    week: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
+                        roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) : roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
+                    month: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
+                        roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) : roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
+                    total: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
+                        roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) : roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
                 )
 
-                // TIR calcs for every case
-                var oneDay_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
-                var sevenDays_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
-                var thirtyDays_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
-                var totalDays_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
-
-                // Get all TIR calcs for every case
-                if nr_bgs > 0 {
-                    oneDay_ = tir(bgArray_1)
-                    sevenDays_ = tir(bgArray_7)
-                    thirtyDays_ = tir(bgArray_30)
-                    totalDays_ = tir(bgArrayForTIR)
-                }
+                var oneDay_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
+                var sevenDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
+                var thirtyDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
+                var totalDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
+                // Get TIR computations for every case
+                oneDay_ = tir(glucose_24)
+                sevenDays_ = tir(glucose_7)
+                thirtyDays_ = tir(glucose_30)
+                totalDays_ = tir(glucose)
 
                 let tir = Durations(
                     day: roundDecimal(Decimal(oneDay_.TIR), 1),
@@ -1110,53 +1034,89 @@ final class BaseAPSManager: APSManager, Injectable {
                     month: roundDecimal(Decimal(thirtyDays_.TIR), 1),
                     total: roundDecimal(Decimal(totalDays_.TIR), 1)
                 )
-
                 let hypo = Durations(
                     day: Decimal(oneDay_.hypos),
                     week: Decimal(sevenDays_.hypos),
                     month: Decimal(thirtyDays_.hypos),
                     total: Decimal(totalDays_.hypos)
                 )
-
                 let hyper = Durations(
                     day: Decimal(oneDay_.hypers),
                     week: Decimal(sevenDays_.hypers),
                     month: Decimal(thirtyDays_.hypers),
                     total: Decimal(totalDays_.hypers)
                 )
-
+                let normal = Durations(
+                    day: Decimal(oneDay_.normal_),
+                    week: Decimal(sevenDays_.normal_),
+                    month: Decimal(thirtyDays_.normal_),
+                    total: Decimal(totalDays_.normal_)
+                )
                 let range = Threshold(
                     low: units == .mmolL ? roundDecimal(settingsManager.settings.low.asMmolL, 1) :
                         roundDecimal(settingsManager.settings.low, 0),
                     high: units == .mmolL ? roundDecimal(settingsManager.settings.high.asMmolL, 1) :
                         roundDecimal(settingsManager.settings.high, 0)
                 )
-
                 let TimeInRange = TIRs(
                     TIR: tir,
                     Hypos: hypo,
                     Hypers: hyper,
-                    Threshold: range
+                    Threshold: range,
+                    Euglycemic: normal
                 )
-
                 let avgs = Durations(
-                    day: roundDecimal(bg_1, 1),
-                    week: roundDecimal(bg_7, 1),
-                    month: roundDecimal(bg_30, 1),
-                    total: roundDecimal(bg_total, 1)
+                    day: roundDecimal(Decimal(oneDayGlucose.average), 1),
+                    week: roundDecimal(Decimal(sevenDaysGlucose.average), 1),
+                    month: roundDecimal(Decimal(thirtyDaysGlucose.average), 1),
+                    total: roundDecimal(Decimal(totalDaysGlucose.average), 1)
                 )
+                let avg = Averages(Average: avgs, Median: median)
+                // Standard Deviations
+                let standardDeviations = Durations(
+                    day: roundDecimal(Decimal(oneDayGlucose.sd), 1),
+                    week: roundDecimal(Decimal(sevenDaysGlucose.sd), 1),
+                    month: roundDecimal(Decimal(thirtyDaysGlucose.sd), 1),
+                    total: roundDecimal(Decimal(totalDaysGlucose.sd), 1)
+                )
+                // CV = standard deviation / sample mean x 100
+                let cvs = Durations(
+                    day: roundDecimal(Decimal(oneDayGlucose.cv), 1),
+                    week: roundDecimal(Decimal(sevenDaysGlucose.cv), 1),
+                    month: roundDecimal(Decimal(thirtyDaysGlucose.cv), 1),
+                    total: roundDecimal(Decimal(totalDaysGlucose.cv), 1)
+                )
+                let variance = Variance(SD: standardDeviations, CV: cvs)
 
-                let saveAverages = BGaverages(context: self.coredataContext)
-                saveAverages.date = Date()
-                saveAverages.average = bg_total as NSDecimalNumber
-                saveAverages.average_1 = bg_1 as NSDecimalNumber
-                saveAverages.average_7 = bg_7 as NSDecimalNumber
-                saveAverages.average_30 = bg_30 as NSDecimalNumber
-                try? self.coredataContext.save()
+                // Loops
+                var lsr = [LoopStatRecord]()
+                let requestLSR = LoopStatRecord.fetchRequest() as NSFetchRequest<LoopStatRecord>
+                requestLSR.predicate = NSPredicate(
+                    format: "interval > 0 AND start > %@",
+                    Date().addingTimeInterval(-24.hours.timeInterval) as NSDate
+                )
+                let sortLSR = NSSortDescriptor(key: "start", ascending: false)
+                requestLSR.sortDescriptors = [sortLSR]
+                try? lsr = coredataContext.fetch(requestLSR)
+                // Compute LoopStats for 24 hours
+                let oneDayLoops = loops(lsr)
+                let loopstat = LoopCycles(
+                    loops: oneDayLoops.loops,
+                    errors: oneDayLoops.errors,
+                    readings: Int(oneDayGlucose.readings),
+                    success_rate: oneDayLoops.success_rate,
+                    avg_interval: oneDayLoops.avg_interval,
+                    median_interval: oneDayLoops.median_interval,
+                    min_interval: oneDayLoops.min_interval,
+                    max_interval: oneDayLoops.max_interval,
+                    avg_duration: oneDayLoops.avg_duration,
+                    median_duration: oneDayLoops.median_duration,
+                    min_duration: oneDayLoops.max_duration,
+                    max_duration: oneDayLoops.max_duration
+                )
 
-                let avg = Averages(Average: avgs, Median: median)
+                // Insulin
                 var insulinDistribution = [InsulinDistribution]()
-
                 var insulin = Ins(
                     TDD: 0,
                     bolus: 0,
@@ -1164,13 +1124,10 @@ final class BaseAPSManager: APSManager, Injectable {
                     scheduled_basal: 0,
                     total_average: 0
                 )
-
                 let requestInsulinDistribution = InsulinDistribution.fetchRequest() as NSFetchRequest<InsulinDistribution>
                 let sortInsulin = NSSortDescriptor(key: "date", ascending: false)
                 requestInsulinDistribution.sortDescriptors = [sortInsulin]
-
                 try? insulinDistribution = coredataContext.fetch(requestInsulinDistribution)
-
                 insulin = Ins(
                     TDD: roundDecimal(currentTDD, 2),
                     bolus: insulinDistribution.first != nil ? ((insulinDistribution.first?.bolus ?? 0) as Decimal) : 0,
@@ -1180,73 +1137,7 @@ final class BaseAPSManager: APSManager, Injectable {
                     total_average: roundDecimal(tddTotalAverage, 1)
                 )
 
-                var sumOfSquares = 0.0
-                var sumOfSquares_1 = 0.0
-                var sumOfSquares_7 = 0.0
-                var sumOfSquares_30 = 0.0
-
-                // Total
-                for array in bgArray {
-                    sumOfSquares += pow(array - Double(bg_total), 2)
-                }
-                // One day
-                for array_1 in bgArray_1_ {
-                    sumOfSquares_1 += pow(array_1 - Double(bg_1), 2)
-                }
-                // week
-                for array_7 in bgArray_7_ {
-                    sumOfSquares_7 += pow(array_7 - Double(bg_7), 2)
-                }
-                // month
-                for array_30 in bgArray_30_ {
-                    sumOfSquares_30 += pow(array_30 - Double(bg_30), 2)
-                }
-
-                // Standard deviation and Coefficient of variation
-                var sd_total = 0.0
-                var cv_total = 0.0
-                var sd_1 = 0.0
-                var cv_1 = 0.0
-                var sd_7 = 0.0
-                var cv_7 = 0.0
-                var sd_30 = 0.0
-                var cv_30 = 0.0
-
-                // Avoid division by zero
-                if bg_total > 0 {
-                    sd_total = sqrt(sumOfSquares / Double(nr_bgs))
-                    cv_total = sd_total / Double(bg_total) * 100
-                }
-                if bg_1 > 0 {
-                    sd_1 = sqrt(sumOfSquares_1 / Double(bgArray_1_.count))
-                    cv_1 = sd_1 / Double(bg_1) * 100
-                }
-                if bg_7 > 0 {
-                    sd_7 = sqrt(sumOfSquares_7 / Double(bgArray_7_.count))
-                    cv_7 = sd_7 / Double(bg_7) * 100
-                }
-                if bg_30 > 0 {
-                    sd_30 = sqrt(sumOfSquares_30 / Double(bgArray_30_.count))
-                    cv_30 = sd_30 / Double(bg_30) * 100
-                }
-
-                // Standard Deviations
-                let standardDeviations = Durations(
-                    day: roundDecimal(Decimal(sd_1), 1),
-                    week: roundDecimal(Decimal(sd_7), 1),
-                    month: roundDecimal(Decimal(sd_30), 1),
-                    total: roundDecimal(Decimal(sd_total), 1)
-                )
-
-                // CV = standard deviation / sample mean x 100
-                let cvs = Durations(
-                    day: roundDecimal(Decimal(cv_1), 1),
-                    week: roundDecimal(Decimal(cv_7), 1),
-                    month: roundDecimal(Decimal(cv_30), 1),
-                    total: roundDecimal(Decimal(cv_total), 1)
-                )
-
-                let variance = Variance(SD: standardDeviations, CV: cvs)
+                let hbA1cUnit = !overrideHbA1cUnit ? (units == .mmolL ? "mmol/mol" : "%") : (units == .mmolL ? "%" : "mmol/mol")
 
                 let dailystat = Statistics(
                     created_at: Date(),
@@ -1268,13 +1159,12 @@ final class BaseAPSManager: APSManager, Injectable {
                     Statistics: Stats(
                         Distribution: TimeInRange,
                         Glucose: avg,
-                        HbA1c: hbs,
+                        HbA1c: hbs, Units: Units(Glucose: units.rawValue, HbA1c: hbA1cUnit),
                         LoopCycles: loopstat,
                         Insulin: insulin,
                         Variance: variance
                     )
                 )
-
                 storage.save(dailystat, as: file)
                 nightscout.uploadStatistics(dailystat: dailystat)
                 nightscout.uploadPreferences()

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

@@ -32,7 +32,9 @@ final class BaseFetchAnnouncementsManager: FetchAnnouncementsManager, Injectable
                 return self.nightscoutManager.fetchAnnouncements()
             }
             .sink { announcements in
-                guard let last = announcements.filter({ $0.createdAt > self.announcementsStorage.syncDate() }).last
+                guard let last = announcements.filter({ $0.createdAt > self.announcementsStorage.syncDate() })
+                    .sorted(by: { $0.createdAt < $1.createdAt })
+                    .last
                 else { return }
 
                 self.announcementsStorage.storeAnnouncements([last], enacted: false)

+ 9 - 4
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -389,6 +389,7 @@ final class OpenAPS {
                 let tempTargets = self.loadFileFromStorage(name: Settings.tempTargets)
                 let model = self.loadFileFromStorage(name: Settings.model)
                 let autotune = useAutotune ? self.loadFileFromStorage(name: Settings.autotune) : .empty
+                let freeaps = self.loadFileFromStorage(name: FreeAPS.settings)
 
                 let pumpProfile = self.makeProfile(
                     preferences: preferences,
@@ -399,7 +400,8 @@ final class OpenAPS {
                     carbRatio: cr,
                     tempTargets: tempTargets,
                     model: model,
-                    autotune: RawJSON.null
+                    autotune: RawJSON.null,
+                    freeaps: freeaps
                 )
 
                 let profile = self.makeProfile(
@@ -411,7 +413,8 @@ final class OpenAPS {
                     carbRatio: cr,
                     tempTargets: tempTargets,
                     model: model,
-                    autotune: autotune.isEmpty ? .null : autotune
+                    autotune: autotune.isEmpty ? .null : autotune,
+                    freeaps: freeaps
                 )
 
                 self.storage.save(pumpProfile, as: Settings.pumpProfile)
@@ -598,7 +601,8 @@ final class OpenAPS {
         carbRatio: JSON,
         tempTargets: JSON,
         model: JSON,
-        autotune: JSON
+        autotune: JSON,
+        freeaps: JSON
     ) -> RawJSON {
         dispatchPrecondition(condition: .onQueue(processQueue))
         return jsWorker.inCommonContext { worker in
@@ -616,7 +620,8 @@ final class OpenAPS {
                     carbRatio,
                     tempTargets,
                     model,
-                    autotune
+                    autotune,
+                    freeaps
                 ]
             )
         }

+ 86 - 12
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -19,6 +19,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     private let processQueue = DispatchQueue(label: "BaseCarbsStorage.processQueue")
     @Injected() private var storage: FileStorage!
     @Injected() private var broadcaster: Broadcaster!
+    @Injected() private var settings: SettingsManager!
 
     let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
@@ -26,25 +27,97 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         injectServices(resolver)
     }
 
-    func storeCarbs(_ carbs: [CarbsEntry]) {
+    func storeCarbs(_ entries: [CarbsEntry]) {
         processQueue.sync {
             let file = OpenAPS.Monitor.carbHistory
             var uniqEvents: [CarbsEntry] = []
-            self.storage.transaction { storage in
-                storage.append(carbs, to: file, uniqBy: \.createdAt)
-                uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
-                    .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
-                    .sorted { $0.createdAt > $1.createdAt } ?? []
-                storage.save(Array(uniqEvents), as: file)
+
+            let fat = entries.last?.fat ?? 0
+            let protein = entries.last?.protein ?? 0
+
+            if fat > 0 || protein > 0 {
+                // -------------------------- FPU--------------------------------------
+                let interval = settings.settings.minuteInterval // Interval betwwen carbs
+                let timeCap = settings.settings.timeCap // Max Duration
+                let adjustment = settings.settings.individualAdjustmentFactor
+                let delay = settings.settings.delay // Tme before first future carb entry
+                let kcal = protein * 4 + fat * 9
+                let carbEquivalents = (kcal / 10) * adjustment
+                let fpus = carbEquivalents / 10
+                // Duration in hours used for extended boluses with Warsaw Method. Here used for total duration of the computed carbquivalents instead, excluding the configurable delay.
+                var computedDuration = 0
+                switch fpus {
+                case ..<2:
+                    computedDuration = 3
+                case 2 ..< 3:
+                    computedDuration = 4
+                case 3 ..< 4:
+                    computedDuration = 5
+                default:
+                    computedDuration = timeCap
+                }
+                // Size of each created carb equivalent if 60 minutes interval
+                var equivalent: Decimal = carbEquivalents / Decimal(computedDuration)
+                // Adjust for interval setting other than 60 minutes
+                equivalent /= Decimal(60 / interval)
+                // Round to 1 fraction digit
+                // equivalent = Decimal(round(Double(equivalent * 10) / 10))
+                let roundedEquivalent: Double = round(Double(equivalent * 10)) / 10
+                equivalent = Decimal(roundedEquivalent)
+                // Number of equivalents
+                var numberOfEquivalents = carbEquivalents / equivalent
+                // Only use delay in first loop
+                var firstIndex = true
+                // New date for each carb equivalent
+                var useDate = entries.last?.createdAt ?? Date()
+                // Group and Identify all FPUs together
+                let fpuID = UUID().uuidString
+                // Create an array of all future carb equivalents.
+                var futureCarbArray = [CarbsEntry]()
+                while carbEquivalents > 0, numberOfEquivalents > 0 {
+                    if firstIndex {
+                        useDate = useDate.addingTimeInterval(delay.minutes.timeInterval)
+                        firstIndex = false
+                    } else { useDate = useDate.addingTimeInterval(interval.minutes.timeInterval) }
+
+                    let eachCarbEntry = CarbsEntry(
+                        id: UUID().uuidString, createdAt: useDate, carbs: equivalent, fat: 0, protein: 0, note: nil,
+                        enteredBy: CarbsEntry.manual, isFPU: true,
+                        fpuID: fpuID
+                    )
+                    futureCarbArray.append(eachCarbEntry)
+                    numberOfEquivalents -= 1
+                }
+                // Save the array
+                if carbEquivalents > 0 {
+                    self.storage.transaction { storage in
+                        storage.append(futureCarbArray, to: file, uniqBy: \.createdAt)
+                        uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
+                            .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
+                            .sorted { $0.createdAt > $1.createdAt } ?? []
+                        storage.save(Array(uniqEvents), as: file)
+                    }
+                }
+            } // ------------------------- END OF TPU ----------------------------------------
+            // Store the actual (normal) carbs
+            if entries.last?.carbs ?? 0 > 0 {
+                uniqEvents = []
+                self.storage.transaction { storage in
+                    storage.append(entries, to: file, uniqBy: \.createdAt)
+                    uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
+                        .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
+                        .sorted { $0.createdAt > $1.createdAt } ?? []
+                    storage.save(Array(uniqEvents), as: file)
+                }
             }
 
             // MARK: Save to CoreData. TEST
 
             var cbs: Decimal = 0
             var carbDate = Date()
-            if carbs.isNotEmpty {
-                cbs = carbs[0].carbs
-                carbDate = carbs[0].createdAt
+            if entries.isNotEmpty {
+                cbs = entries[0].carbs
+                carbDate = entries[0].createdAt
             }
             if cbs != 0 {
                 self.coredataContext.perform {
@@ -56,7 +129,6 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     try? self.coredataContext.save()
                 }
             }
-
             broadcaster.notify(CarbsObserver.self, on: processQueue) {
                 $0.carbsDidUpdate(uniqEvents)
             }
@@ -113,8 +185,10 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 enteredBy: CarbsEntry.manual,
                 bolus: nil,
                 insulin: nil,
-                notes: nil,
                 carbs: $0.carbs,
+                fat: nil,
+                protein: nil,
+                foodType: $0.note,
                 targetTop: nil,
                 targetBottom: nil
             )

+ 2 - 0
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -117,6 +117,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                         insulin: nil,
                         notes: notes,
                         carbs: nil,
+                        fat: nil,
+                        protein: nil,
                         targetTop: nil,
                         targetBottom: nil
                     )

+ 12 - 0
FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -230,6 +230,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                     insulin: nil,
                     notes: nil,
                     carbs: nil,
+                    fat: nil,
+                    protein: nil,
                     targetTop: nil,
                     targetBottom: nil
                 ))
@@ -260,6 +262,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                     insulin: event.amount,
                     notes: nil,
                     carbs: nil,
+                    fat: nil,
+                    protein: nil,
                     targetTop: nil,
                     targetBottom: nil
                 )
@@ -277,6 +281,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                     insulin: nil,
                     notes: nil,
                     carbs: Decimal(event.carbInput ?? 0),
+                    fat: nil,
+                    protein: nil,
                     targetTop: nil,
                     targetBottom: nil
                 )
@@ -300,6 +306,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                     insulin: nil,
                     notes: nil,
                     carbs: nil,
+                    fat: nil,
+                    protein: nil,
                     targetTop: nil,
                     targetBottom: nil
                 )
@@ -317,6 +325,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                     insulin: nil,
                     notes: nil,
                     carbs: nil,
+                    fat: nil,
+                    protein: nil,
                     targetTop: nil,
                     targetBottom: nil
                 )
@@ -334,6 +344,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                     insulin: nil,
                     notes: "Alarm \(String(describing: event.note)) \(event.type)",
                     carbs: nil,
+                    fat: nil,
+                    protein: nil,
                     targetTop: nil,
                     targetBottom: nil
                 )

+ 1 - 0
FreeAPS/Sources/Helpers/Color+Extensions.swift

@@ -62,4 +62,5 @@ extension Color {
     static let loopPink = Color("LoopPink")
     static let lemon = Color("Lemon")
     static let minus = Color("minus")
+    static let darkGray = Color("darkGray")
 }

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 4
FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 8
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 16 - 7
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 22 - 8
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 22 - 8
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 44 - 30
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 23 - 9
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 216 - 202
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 7
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings


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

@@ -4,6 +4,9 @@ struct CarbsEntry: JSON, Equatable, Hashable {
     let id: String?
     let createdAt: Date
     let carbs: Decimal
+    let fat: Decimal?
+    let protein: Decimal?
+    let note: String?
     let enteredBy: String?
     let isFPU: Bool?
     let fpuID: String?
@@ -25,6 +28,9 @@ extension CarbsEntry {
         case id = "_id"
         case createdAt = "created_at"
         case carbs
+        case fat
+        case protein
+        case note
         case enteredBy
         case isFPU
         case fpuID

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

@@ -41,6 +41,8 @@ struct FreeAPSSettings: JSON, Equatable {
     var oneDimensionalGraph: Bool = false
     var rulerMarks: Bool = false
     var maxCarbs: Decimal = 1000
+    var displayFatAndProteinOnWatch: Bool = false
+    var onlyAutotuneBasals: Bool = false
 }
 
 extension FreeAPSSettings: Decodable {
@@ -214,6 +216,14 @@ extension FreeAPSSettings: Decodable {
             settings.maxCarbs = maxCarbs
         }
 
+        if let displayFatAndProteinOnWatch = try? container.decode(Bool.self, forKey: .displayFatAndProteinOnWatch) {
+            settings.displayFatAndProteinOnWatch = displayFatAndProteinOnWatch
+        }
+
+        if let onlyAutotuneBasals = try? container.decode(Bool.self, forKey: .onlyAutotuneBasals) {
+            settings.onlyAutotuneBasals = onlyAutotuneBasals
+        }
+
         self = settings
     }
 }

+ 15 - 0
FreeAPS/Sources/Models/Loops.swift

@@ -0,0 +1,15 @@
+import Foundation
+
+struct Loops: JSON, Equatable {
+    var loops: Int
+    var errors: Int
+    var success_rate: Decimal
+    var avg_interval: Decimal
+    var median_interval: Decimal
+    var min_interval: Decimal
+    var max_interval: Decimal
+    var avg_duration: Decimal
+    var median_duration: Decimal
+    var min_duration: Decimal
+    var max_duration: Decimal
+}

+ 6 - 0
FreeAPS/Sources/Models/NightscoutTreatment.swift

@@ -13,6 +13,9 @@ struct NigtscoutTreatment: JSON, Hashable, Equatable {
     var insulin: Decimal?
     var notes: String?
     var carbs: Decimal?
+    var fat: Decimal?
+    var protein: Decimal?
+    var foodType: String?
     let targetTop: Decimal?
     let targetBottom: Decimal?
 
@@ -43,6 +46,9 @@ extension NigtscoutTreatment {
         case insulin
         case notes
         case carbs
+        case fat
+        case protein
+        case foodType
         case targetTop
         case targetBottom
     }

+ 9 - 0
FreeAPS/Sources/Models/Statistics.swift

@@ -115,6 +115,11 @@ struct Durations: JSON, Equatable {
     var total: Decimal
 }
 
+struct Units: JSON, Equatable {
+    var Glucose: String
+    var HbA1c: String
+}
+
 struct Threshold: JSON, Equatable {
     var low: Decimal
     var high: Decimal
@@ -125,6 +130,7 @@ struct TIRs: JSON, Equatable {
     var Hypos: Durations
     var Hypers: Durations
     var Threshold: Threshold
+    var Euglycemic: Durations
 }
 
 struct Ins: JSON, Equatable {
@@ -144,6 +150,7 @@ struct Stats: JSON, Equatable {
     var Distribution: TIRs
     var Glucose: Averages
     var HbA1c: Durations
+    var Units: Units
     var LoopCycles: LoopCycles
     var Insulin: Ins
     var Variance: Variance
@@ -179,6 +186,7 @@ extension TIRs {
         case Hypos
         case Hypers
         case Threshold
+        case Euglycemic
     }
 }
 
@@ -204,6 +212,7 @@ extension Stats {
         case Distribution
         case Glucose
         case HbA1c
+        case Units
         case LoopCycles
         case Insulin
         case Variance

+ 2 - 0
FreeAPS/Sources/Models/Suggestion.swift

@@ -28,6 +28,7 @@ struct Suggestion: JSON, Equatable {
     let expectedDelta: Decimal?
     let minGuardBG: Decimal?
     let minPredBG: Decimal?
+    let threshold: Decimal?
 }
 
 struct Predictions: JSON, Equatable {
@@ -73,6 +74,7 @@ extension Suggestion {
         case expectedDelta
         case minGuardBG
         case minPredBG
+        case threshold
     }
 }
 

+ 13 - 73
FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift

@@ -17,6 +17,7 @@ extension AddCarbs {
         @Published var selection: Presets?
         @Published var summation: [String] = []
         @Published var maxCarbs: Decimal = 0
+        @Published var note: String = ""
 
         let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
 
@@ -33,79 +34,18 @@ extension AddCarbs {
             }
             carbs = min(carbs, maxCarbs)
 
-            if useFPUconversion {
-                // -------------------------- FPU--------------------------------------
-                let interval = settings.settings.minuteInterval // Interval betwwen carbs
-                let timeCap = settings.settings.timeCap // Max Duration
-                let adjustment = settings.settings.individualAdjustmentFactor
-                let delay = settings.settings.delay // Tme before first future carb entry
-
-                let kcal = protein * 4 + fat * 9
-                let carbEquivalents = (kcal / 10) * adjustment
-                let fpus = carbEquivalents / 10
-
-                // Duration in hours used for extended boluses with Warsaw Method. Here used for total duration of the computed carbquivalents instead, excluding the configurable delay.
-                var computedDuration = 0
-                switch fpus {
-                case ..<2:
-                    computedDuration = 3
-                case 2 ..< 3:
-                    computedDuration = 4
-                case 3 ..< 4:
-                    computedDuration = 5
-                default:
-                    computedDuration = timeCap
-                }
-
-                // Size of each created carb equivalent if 60 minutes interval
-                var equivalent: Decimal = carbEquivalents / Decimal(computedDuration)
-                // Adjust for interval setting other than 60 minutes
-                equivalent /= Decimal(60 / interval)
-                // Round to 1 fraction digit
-                // equivalent = Decimal(round(Double(equivalent * 10) / 10))
-                let roundedEquivalent: Double = round(Double(equivalent * 10)) / 10
-                equivalent = Decimal(roundedEquivalent)
-                // Number of equivalents
-                var numberOfEquivalents = carbEquivalents / equivalent
-                // Only use delay in first loop
-                var firstIndex = true
-                // New date for each carb equivalent
-                var useDate = date
-                // Group and Identify all FPUs together
-                let fpuID = UUID().uuidString
-
-                // Create an array of all future carb equivalents.
-                var futureCarbArray = [CarbsEntry]()
-                while carbEquivalents > 0, numberOfEquivalents > 0 {
-                    if firstIndex {
-                        useDate = useDate.addingTimeInterval(delay.minutes.timeInterval)
-                        firstIndex = false
-                    } else { useDate = useDate.addingTimeInterval(interval.minutes.timeInterval) }
-
-                    let eachCarbEntry = CarbsEntry(
-                        id: UUID().uuidString, createdAt: useDate, carbs: equivalent, enteredBy: CarbsEntry.manual, isFPU: true,
-                        fpuID: fpuID
-                    )
-                    futureCarbArray.append(eachCarbEntry)
-                    numberOfEquivalents -= 1
-                }
-                // Save the array
-                if carbEquivalents > 0 {
-                    carbsStorage.storeCarbs(futureCarbArray)
-                }
-            } // ------------------------- END OF TPU ----------------------------------------
-
-            // Store the real carbs
-            if carbs > 0 {
-                carbsStorage
-                    .storeCarbs([CarbsEntry(
-                        id: UUID().uuidString,
-                        createdAt: date,
-                        carbs: carbs,
-                        enteredBy: CarbsEntry.manual,
-                        isFPU: false, fpuID: nil
-                    )])
-            }
+            carbsStorage.storeCarbs(
+                [CarbsEntry(
+                    id: UUID().uuidString,
+                    createdAt: date,
+                    carbs: carbs,
+                    fat: fat,
+                    protein: protein,
+                    note: note,
+                    enteredBy: CarbsEntry.manual,
+                    isFPU: false, fpuID: nil
+                )]
+            )
 
             if settingsManager.settings.skipBolusScreenAfterCarbs {
                 apsManager.determineBasalSync()

+ 13 - 4
FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift

@@ -10,6 +10,7 @@ extension AddCarbs {
         @State var isPromtPresented = false
         @State var saved = false
         @State private var showAlert = false
+        @FocusState private var isFocused: Bool
 
         @FetchRequest(
             entity: Presets.entity(),
@@ -54,6 +55,14 @@ extension AddCarbs {
                         proteinAndFat()
                     }
                     HStack {
+                        Text("Note").foregroundColor(.secondary)
+                        TextField("", text: $state.note).multilineTextAlignment(.trailing)
+                        if state.note != "", isFocused {
+                            Button { isFocused = false } label: { Image(systemName: "keyboard.chevron.compact.down") }
+                                .controlSize(.mini)
+                        }
+                    }.focused($isFocused)
+                    HStack {
                         Button {
                             state.useFPUconversion.toggle()
                         }
@@ -107,12 +116,12 @@ extension AddCarbs {
                     DatePicker("Date", selection: $state.date)
                 }
 
-                Section(footer: Text(state.waitersNotepad().description)) {
+                Section {
                     Button { state.add() }
                     label: { Text("Save and continue").font(.title3) }
                         .disabled(state.carbs <= 0 && state.fat <= 0 && state.protein <= 0)
                         .frame(maxWidth: .infinity, alignment: .center)
-                }
+                } footer: { Text(state.waitersNotepad().description) }
 
                 if !state.useFPUconversion {
                     Section {
@@ -126,7 +135,7 @@ extension AddCarbs {
 
         var presetPopover: some View {
             Form {
-                Section(header: Text("Enter Meal Preset Name")) {
+                Section {
                     TextField("Name Of Dish", text: $dish)
                     Button {
                         saved = true
@@ -148,7 +157,7 @@ extension AddCarbs {
                         saved = false
                         isPromtPresented = false }
                     label: { Text("Cancel") }
-                }
+                } header: { Text("Enter Meal Preset Name") }
             }
         }
 

+ 2 - 0
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigStateModel.swift

@@ -5,6 +5,7 @@ extension AutotuneConfig {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var apsManager: APSManager!
         @Published var useAutotune = false
+        @Published var onlyAutotuneBasals = false
         @Published var autotune: Autotune?
         private(set) var units: GlucoseUnits = .mmolL
         @Published var publishedDate = Date()
@@ -21,6 +22,7 @@ extension AutotuneConfig {
             units = settingsManager.settings.units
             useAutotune = settingsManager.settings.useAutotune
             publishedDate = lastAutotuneDate
+            subscribeSetting(\.onlyAutotuneBasals, on: $onlyAutotuneBasals) { onlyAutotuneBasals = $0 }
 
             $useAutotune
                 .removeDuplicates()

+ 20 - 15
FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift

@@ -31,6 +31,9 @@ extension AutotuneConfig {
             Form {
                 Section {
                     Toggle("Use Autotune", isOn: $state.useAutotune)
+                    if state.useAutotune {
+                        Toggle("Only Autotune Basal Insulin", isOn: $state.onlyAutotuneBasals)
+                    }
                 }
 
                 Section {
@@ -44,22 +47,24 @@ extension AutotuneConfig {
                 }
 
                 if let autotune = state.autotune {
-                    Section {
-                        HStack {
-                            Text("Carb ratio")
-                            Spacer()
-                            Text(isfFormatter.string(from: autotune.carbRatio as NSNumber) ?? "0")
-                            Text("g/U").foregroundColor(.secondary)
-                        }
-                        HStack {
-                            Text("Sensitivity")
-                            Spacer()
-                            if state.units == .mmolL {
-                                Text(isfFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
-                            } else {
-                                Text(isfFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
+                    if !state.onlyAutotuneBasals {
+                        Section {
+                            HStack {
+                                Text("Carb ratio")
+                                Spacer()
+                                Text(isfFormatter.string(from: autotune.carbRatio as NSNumber) ?? "0")
+                                Text("g/U").foregroundColor(.secondary)
+                            }
+                            HStack {
+                                Text("Sensitivity")
+                                Spacer()
+                                if state.units == .mmolL {
+                                    Text(isfFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
+                                } else {
+                                    Text(isfFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
+                                }
+                                Text(state.units.rawValue + "/U").foregroundColor(.secondary)
                             }
-                            Text(state.units.rawValue + "/U").foregroundColor(.secondary)
                         }
                     }
 

+ 1 - 2
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -33,8 +33,7 @@ extension Bolus {
             broadcaster.register(SuggestionObserver.self, observer: self)
             units = settingsManager.settings.units
             percentage = settingsManager.settings.insulinReqPercentage
-            threshold = units == .mmolL ? settingsManager.preferences.threshold_setting.asMmolL : settingsManager.preferences
-                .threshold_setting
+            threshold = provider.suggestion?.threshold ?? 0
 
             if waitForSuggestionInitial {
                 apsManager.determineBasal()

+ 1 - 1
FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift

@@ -248,7 +248,7 @@ extension Bolus {
                         "which is below your Threshold (",
                         comment: "Bolus pop-up / Alert string. Make translations concise!"
                     ) + state
-                    .threshold.formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))) + ")"
+                    .threshold.formatted() + " " + state.units.rawValue + ")"
             case 3:
                 return NSLocalizedString(
                     "Eventual Glucose > Target Glucose, but glucose is climbing slower than expected. Expected: ",

+ 0 - 13
FreeAPS/Sources/Modules/CGM/CGMStateModel.swift

@@ -28,18 +28,6 @@ extension CGM {
             cgmTransmitterDeviceAddress = UserDefaults.standard.cgmTransmitterDeviceAddress
 
             subscribeSetting(\.useCalendar, on: $createCalendarEvents) { createCalendarEvents = $0 }
-            subscribeSetting(\.uploadGlucose, on: $uploadGlucose, initial: { uploadGlucose = $0 }, didSet: { val in
-                if let cgmManagerG5 = self.cgmManager.glucoseSource.cgmManager as? G5CGMManager {
-                    cgmManagerG5.shouldSyncToRemoteService = val
-                }
-                if let cgmManagerG6 = self.cgmManager.glucoseSource.cgmManager as? G6CGMManager {
-                    cgmManagerG6.shouldSyncToRemoteService = val
-                }
-                if let cgmManagerG7 = self.cgmManager.glucoseSource.cgmManager as? G7CGMManager {
-                    cgmManagerG7.uploadReadings = val
-                }
-            })
-
             subscribeSetting(\.smoothGlucose, on: $smoothGlucose, initial: { smoothGlucose = $0 })
 
             $cgm
@@ -91,7 +79,6 @@ extension CGM.StateModel: CompletionDelegate {
         }
         // refresh the upload options
         uploadGlucose = settingsManager.settings.uploadGlucose
-
         cgmManager.updateGlucoseSource()
     }
 }

+ 0 - 4
FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift

@@ -64,10 +64,6 @@ extension CGM {
                         }
                     }
 
-                    Section(header: Text("Other")) {
-                        Toggle("Upload glucose to Nightscout", isOn: $state.uploadGlucose)
-                    }
-
                     Section(header: Text("Experimental")) {
                         Toggle("Smooth Glucose Value", isOn: $state.smoothGlucose)
                     }

+ 1 - 1
FreeAPS/Sources/Modules/CREditor/CREditorStateModel.swift

@@ -7,7 +7,7 @@ extension CREditor {
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 
-        let rateValues = stride(from: 30.0, to: 501.0, by: 1.0).map { ($0.decimal ?? .zero) / 10 }
+        let rateValues = stride(from: 15.0, to: 501.0, by: 1.0).map { ($0.decimal ?? .zero) / 10 }
 
         var canAdd: Bool {
             guard let lastItem = items.last else { return true }

+ 1 - 1
FreeAPS/Sources/Modules/CREditor/View/CREditorRootView.swift

@@ -22,7 +22,7 @@ extension CREditor {
 
         var body: some View {
             Form {
-                if let autotune = state.autotune {
+                if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
                     Section(header: Text("Autotune")) {
                         HStack {
                             Text("Calculated Ratio")

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

@@ -65,6 +65,7 @@ enum DataTable {
         let duration: Decimal?
         let isFPU: Bool?
         let fpuID: String?
+        let note: String?
 
         private var numberFormater: NumberFormatter {
             let formatter = NumberFormatter()
@@ -90,7 +91,8 @@ enum DataTable {
             id: String? = nil,
             idPumpEvent: String? = nil,
             isFPU: Bool? = false,
-            fpuID: String? = nil
+            fpuID: String? = nil,
+            note: String? = nil
         ) {
             self.units = units
             self.type = type
@@ -102,6 +104,7 @@ enum DataTable {
             self.idPumpEvent = idPumpEvent
             self.isFPU = isFPU
             self.fpuID = fpuID
+            self.note = note
         }
 
         static func == (lhs: Treatment, rhs: Treatment) -> Bool {

+ 5 - 3
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -40,10 +40,11 @@ extension DataTable {
                                 type: .carbs,
                                 date: $0.createdAt,
                                 amount: $0.carbs,
-                                id: id
+                                id: id,
+                                note: $0.note
                             )
                         } else {
-                            return Treatment(units: units, type: .carbs, date: $0.createdAt, amount: $0.carbs)
+                            return Treatment(units: units, type: .carbs, date: $0.createdAt, amount: $0.carbs, note: $0.note)
                         }
                     }
 
@@ -57,7 +58,8 @@ extension DataTable {
                             amount: $0.carbs,
                             id: $0.id,
                             isFPU: $0.isFPU,
-                            fpuID: $0.fpuID
+                            fpuID: $0.fpuID,
+                            note: $0.note
                         )
                     }
 

+ 4 - 0
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -120,6 +120,10 @@ extension DataTable {
                 }
 
                 if item.type == .carbs {
+                    if item.note != "" {
+                        Spacer()
+                        Text(item.note ?? "").foregroundColor(.brown)
+                    }
                     Spacer()
                     Image(systemName: "xmark.circle").foregroundColor(.secondary)
                         .contentShape(Rectangle())

+ 1 - 1
FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift

@@ -13,7 +13,7 @@ extension AppleHealthKit {
                     HStack {
                         Image(systemName: "pencil.circle.fill")
                         Text(
-                            "After you create glucose records in the Health app, please open iAPS to help us guaranteed transfer changed data"
+                            "This allows iAPS to read from and write to Apple Heath. You must also give permissions in Settings > Health > Data Access. If you enter a glucose value into Apple Health, open iAPS to confirm it shows up."
                         )
                         .font(.caption)
                     }

+ 16 - 6
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -495,7 +495,7 @@ extension Home {
                                     .background(Capsule().fill(Color.red))
                             }
                         }
-                    }
+                    }.buttonStyle(.borderless)
                     Spacer()
                     Button { state.showModal(for: .addTempTarget) }
                     label: {
@@ -504,7 +504,9 @@ extension Home {
                             .resizable()
                             .frame(width: 24, height: 24)
                             .padding(8)
-                    }.foregroundColor(.loopGreen)
+                    }
+                    .foregroundColor(.loopGreen)
+                    .buttonStyle(.borderless)
                     Spacer()
                     Button { state.showModal(for: .bolus(waitForSuggestion: false)) }
                     label: {
@@ -513,7 +515,9 @@ extension Home {
                             .resizable()
                             .frame(width: 24, height: 24)
                             .padding(8)
-                    }.foregroundColor(.insulin)
+                    }
+                    .foregroundColor(.insulin)
+                    .buttonStyle(.borderless)
                     Spacer()
                     if state.allowManualTemp {
                         Button { state.showModal(for: .manualTempBasal) }
@@ -523,7 +527,9 @@ extension Home {
                                 .resizable()
                                 .frame(width: 24, height: 24)
                                 .padding(8)
-                        }.foregroundColor(.insulin)
+                        }
+                        .foregroundColor(.insulin)
+                        .buttonStyle(.borderless)
                         Spacer()
                     }
                     Button { state.showModal(for: .statistics)
@@ -534,7 +540,9 @@ extension Home {
                             .resizable()
                             .frame(width: 24, height: 24)
                             .padding(8)
-                    }.foregroundColor(.purple)
+                    }
+                    .foregroundColor(.purple)
+                    .buttonStyle(.borderless)
                     Spacer()
                     Button { state.showModal(for: .settings) }
                     label: {
@@ -543,7 +551,9 @@ extension Home {
                             .resizable()
                             .frame(width: 24, height: 24)
                             .padding(8)
-                    }.foregroundColor(.loopGray)
+                    }
+                    .foregroundColor(.loopGray)
+                    .buttonStyle(.borderless)
                 }
                 .padding(.horizontal, 24)
                 .padding(.bottom, geo.safeAreaInsets.bottom)

+ 1 - 0
FreeAPS/Sources/Modules/ISFEditor/ISFEditorDataFlow.swift

@@ -28,4 +28,5 @@ protocol ISFEditorProvider: Provider {
     func saveProfile(_ profile: InsulinSensitivities)
     var autosense: Autosens { get }
     var autotune: Autotune? { get }
+    var suggestion: Suggestion? { get }
 }

+ 4 - 0
FreeAPS/Sources/Modules/ISFEditor/ISFEditorProvider.swift

@@ -20,6 +20,10 @@ extension ISFEditor {
                 ?? Autosens(ratio: 1, newisf: nil, timestamp: nil)
         }
 
+        var suggestion: Suggestion? {
+            storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
+        }
+
         var autotune: Autotune? {
             storage.retrieve(OpenAPS.Settings.autotune, as: Autotune.self)
         }

+ 21 - 4
FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -23,7 +23,7 @@ extension ISFEditor {
 
         var body: some View {
             Form {
-                if let autotune = state.autotune {
+                if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
                     Section(header: Text("Autotune")) {
                         HStack {
                             Text("Calculated Sensitivity")
@@ -38,16 +38,33 @@ extension ISFEditor {
                     }
                 }
                 if let newISF = state.autosensISF {
-                    Section(header: Text("Autosens")) {
+                    Section(
+                        header: !state.settingsManager.preferences
+                            .useNewFormula ? Text("Autosens") : Text("Dynamic Sensitivity")
+                    ) {
+                        let dynamicRatio = state.provider.suggestion?.sensitivityRatio ?? 0
+                        let dynamicISF = state.provider.suggestion?.isf ?? 0
                         HStack {
                             Text("Sensitivity Ratio")
                             Spacer()
-                            Text(rateFormatter.string(from: state.autosensRatio as NSNumber) ?? "1")
+                            Text(
+                                rateFormatter
+                                    .string(from: (
+                                        !state.settingsManager.preferences.useNewFormula ? state
+                                            .autosensRatio : dynamicRatio
+                                    ) as NSNumber) ?? "1"
+                            )
                         }
                         HStack {
                             Text("Calculated Sensitivity")
                             Spacer()
-                            Text(rateFormatter.string(from: newISF as NSNumber) ?? "0")
+                            Text(
+                                rateFormatter
+                                    .string(from: (
+                                        !state.settingsManager.preferences
+                                            .useNewFormula ? newISF : dynamicISF
+                                    ) as NSNumber) ?? "0"
+                            )
                             Text(state.units.rawValue + "/U").foregroundColor(.secondary)
                         }
                     }

+ 18 - 2
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -1,4 +1,6 @@
+import CGMBLEKit
 import Combine
+import G7SensorKit
 import SwiftDate
 import SwiftUI
 
@@ -8,14 +10,16 @@ extension NightscoutConfig {
         @Injected() private var nightscoutManager: NightscoutManager!
         @Injected() private var glucoseStorage: GlucoseStorage!
         @Injected() private var healthKitManager: HealthKitManager!
+        @Injected() private var cgmManager: FetchGlucoseManager!
 
         @Published var url = ""
         @Published var secret = ""
         @Published var message = ""
         @Published var connecting = false
         @Published var backfilling = false
-        @Published var isUploadEnabled = false
-
+        @Published var isUploadEnabled = false // Allow uploads
+        @Published var uploadStats = false // Upload Statistics
+        @Published var uploadGlucose = true // Upload Glucose
         @Published var useLocalSource = false
         @Published var localPort: Decimal = 0
 
@@ -26,6 +30,18 @@ extension NightscoutConfig {
             subscribeSetting(\.isUploadEnabled, on: $isUploadEnabled) { isUploadEnabled = $0 }
             subscribeSetting(\.useLocalGlucoseSource, on: $useLocalSource) { useLocalSource = $0 }
             subscribeSetting(\.localGlucosePort, on: $localPort.map(Int.init)) { localPort = Decimal($0) }
+            subscribeSetting(\.uploadStats, on: $uploadStats) { uploadStats = $0 }
+            subscribeSetting(\.uploadGlucose, on: $uploadGlucose, initial: { uploadGlucose = $0 }, didSet: { val in
+                if let cgmManagerG5 = self.cgmManager.glucoseSource.cgmManager as? G5CGMManager {
+                    cgmManagerG5.shouldSyncToRemoteService = val
+                }
+                if let cgmManagerG6 = self.cgmManager.glucoseSource.cgmManager as? G6CGMManager {
+                    cgmManagerG6.shouldSyncToRemoteService = val
+                }
+                if let cgmManagerG7 = self.cgmManager.glucoseSource.cgmManager as? G7CGMManager {
+                    cgmManagerG7.uploadReadings = val
+                }
+            })
         }
 
         func connect() {

+ 7 - 1
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -44,7 +44,13 @@ extension NightscoutConfig {
                 }
 
                 Section {
-                    Toggle("Allow uploads", isOn: $state.isUploadEnabled)
+                    Toggle("Upload", isOn: $state.isUploadEnabled)
+                    if state.isUploadEnabled {
+                        Toggle("Statistics", isOn: $state.uploadStats)
+                        Toggle("Glucose", isOn: $state.uploadGlucose)
+                    }
+                } header: {
+                    Text("Allow Uploads")
                 }
 
                 Section(header: Text("Local glucose source")) {

+ 5 - 4
FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift

@@ -90,10 +90,11 @@ extension OverrideProfilesConfig {
                 saveOverride.id = id
                 saveOverride.date = Date()
                 if override_target {
-                    if units == .mmolL {
-                        target = target.asMgdL
-                    }
-                    saveOverride.target = target as NSDecimalNumber
+                    saveOverride.target = (
+                        units == .mmolL
+                            ? target.asMgdL
+                            : target
+                    ) as NSDecimalNumber
                 } else { saveOverride.target = 0 }
 
                 if advancedSettings {

+ 1 - 1
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift

@@ -41,7 +41,7 @@ extension PreferencesEditor {
                     displayName: NSLocalizedString("Max COB", comment: "Max COB"),
                     type: .decimal(keypath: \.maxCOB),
                     infoText: NSLocalizedString(
-                        "This defaults maxCOB to 120 because that’s the most a typical body can absorb over 4 hours. (If someone enters more carbs or stacks more; OpenAPS will just truncate dosing based on 120. Essentially, this just limits AMA as a safety cap against weird COB calculations due to fluky data.)",
+                        "The default of maxCOB is 120. (If someone enters more carbs in one or multiple entries, iAPS will cap COB to maxCOB and keep it at maxCOB until the carbs entered above maxCOB have shown to be absorbed. Essentially, this just limits UAM as a safety cap against weird COB calculations due to fluky data.)",
                         comment: "Max COB"
                     ),
                     settable: self

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

@@ -41,7 +41,7 @@ extension Settings {
                     Text("Basal Profile").navigationLink(to: .basalProfileEditor, from: self)
                     Text("Insulin Sensitivities").navigationLink(to: .isfEditor, from: self)
                     Text("Carb Ratios").navigationLink(to: .crEditor, from: self)
-                    Text("Target Ranges").navigationLink(to: .targetsEditor, from: self)
+                    Text("Target Glucose").navigationLink(to: .targetsEditor, from: self)
                     Text("Autotune").navigationLink(to: .autotuneConfig, from: self)
                 }
 

+ 18 - 34
FreeAPS/Sources/Modules/Stat/View/ChartsView.swift

@@ -213,32 +213,23 @@ struct ChartsView: View {
                 let mapGlucoseAcuteLow = mapGlucose.filter({ $0 < Int16(3.3 / 0.0555) })
                 let mapGlucoseHigh = mapGlucose.filter({ $0 > Int16(11 / 0.0555) })
                 let mapGlucoseNormal = mapGlucose.filter({ $0 > Int16(3.8 / 0.0555) && $0 < Int16(7.9 / 0.0555) })
-
                 HStack {
                     let value = Double(mapGlucoseHigh.count * 100 / mapGlucose.count)
-                    if value != 0 {
-                        Text(units == .mmolL ? ">  11  " : ">  200 ").foregroundColor(.secondary)
-                        Text(value.formatted()).foregroundColor(.orange)
-                        Text("%").foregroundColor(.secondary)
-                    }
+                    Text(units == .mmolL ? ">  11  " : ">  198 ").foregroundColor(.secondary)
+                    Text(value.formatted()).foregroundColor(.orange)
+                    Text("%").foregroundColor(.secondary)
                 }.font(.caption)
-
                 HStack {
                     let value = Double(mapGlucoseNormal.count * 100 / mapGlucose.count)
-                    if value != 0 {
-                        Text(units == .mmolL ? "3.9-7.8" : "70-140").foregroundColor(.secondary)
-                        Text(value.formatted()).foregroundColor(.green)
-                        Text("%").foregroundColor(.secondary)
-                    }
+                    Text(units == .mmolL ? "3.9-7.8" : "70-140").foregroundColor(.secondary)
+                    Text(value.formatted()).foregroundColor(.green)
+                    Text("%").foregroundColor(.secondary)
                 }.font(.caption)
-
                 HStack {
                     let value = Double(mapGlucoseAcuteLow.count * 100 / mapGlucose.count)
-                    if value != 0 {
-                        Text(units == .mmolL ? "<  3.3 " : "<  59  ").foregroundColor(.secondary)
-                        Text(value.formatted()).foregroundColor(.red)
-                        Text("%").foregroundColor(.secondary)
-                    }
+                    Text(units == .mmolL ? "<  3.3 " : "<  59  ").foregroundColor(.secondary)
+                    Text(value.formatted()).foregroundColor(.red)
+                    Text("%").foregroundColor(.secondary)
                 }.font(.caption)
             }
         }
@@ -252,32 +243,25 @@ struct ChartsView: View {
                 let mapGlucoseLow = mapGlucose.filter({ $0 < Int16(3.3 / 0.0555) })
                 let mapGlucoseNormal = mapGlucose.filter({ $0 > Int16(3.8 / 0.0555) && $0 < Int16(7.9 / 0.0555) })
                 let mapGlucoseAcuteHigh = mapGlucose.filter({ $0 > Int16(11 / 0.0555) })
-
                 HStack {
                     let value = Double(mapGlucoseLow.count * 100 / mapGlucose.count)
-                    if value != 0 {
-                        Text(units == .mmolL ? "< 3.3" : "< 59").font(.caption2).foregroundColor(.secondary)
-                        Text(value.formatted()).font(.caption).foregroundColor(value == 0 ? .green : .red)
-                        Text("%").font(.caption)
-                    }
+                    Text(units == .mmolL ? "< 3.3" : "< 59").font(.caption2).foregroundColor(.secondary)
+                    Text(value.formatted()).font(.caption).foregroundColor(value == 0 ? .green : .red)
+                    Text("%").font(.caption)
                 }
                 Spacer()
                 HStack {
                     let value = Double(mapGlucoseNormal.count * 100 / mapGlucose.count)
-                    if value != 0 {
-                        Text(units == .mmolL ? "3.9-7.8" : "70-140").foregroundColor(.secondary)
-                        Text(value.formatted()).foregroundColor(.green)
-                        Text("%").foregroundColor(.secondary)
-                    }
+                    Text(units == .mmolL ? "3.9-7.8" : "70-140").foregroundColor(.secondary)
+                    Text(value.formatted()).foregroundColor(.green)
+                    Text("%").foregroundColor(.secondary)
                 }.font(.caption)
                 Spacer()
                 HStack {
                     let value = Double(mapGlucoseAcuteHigh.count * 100 / mapGlucose.count)
-                    if value != 0 {
-                        Text(units == .mmolL ? "> 11.0" : "> 216").font(.caption).foregroundColor(.secondary)
-                        Text(value.formatted()).font(.caption).foregroundColor(value == 0 ? .green : .orange)
-                        Text("%").font(.caption)
-                    }
+                    Text(units == .mmolL ? "> 11.0" : "> 198").font(.caption).foregroundColor(.secondary)
+                    Text(value.formatted()).font(.caption).foregroundColor(value == 0 ? .green : .orange)
+                    Text("%").font(.caption)
                 }
             }
         }

+ 0 - 2
FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift

@@ -5,7 +5,6 @@ extension StatConfig {
         @Published var overrideHbA1cUnit = false
         @Published var low: Decimal = 4 / 0.0555
         @Published var high: Decimal = 10 / 0.0555
-        @Published var uploadStats = false
         @Published var hours: Decimal = 6
         @Published var xGridLines = false
         @Published var yGridLines: Bool = false
@@ -19,7 +18,6 @@ extension StatConfig {
             self.units = units
 
             subscribeSetting(\.overrideHbA1cUnit, on: $overrideHbA1cUnit) { overrideHbA1cUnit = $0 }
-            subscribeSetting(\.uploadStats, on: $uploadStats) { uploadStats = $0 }
             subscribeSetting(\.xGridLines, on: $xGridLines) { xGridLines = $0 }
             subscribeSetting(\.yGridLines, on: $yGridLines) { yGridLines = $0 }
             subscribeSetting(\.rulerMarks, on: $rulerMarks) { rulerMarks = $0 }

+ 0 - 1
FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift

@@ -28,7 +28,6 @@ extension StatConfig {
             Form {
                 Section(header: Text("Settings")) {
                     Toggle("Change HbA1c Unit", isOn: $state.overrideHbA1cUnit)
-                    Toggle("Allow Upload of Statistics to NS", isOn: $state.uploadStats)
                     Toggle("Display Chart X - Grid lines", isOn: $state.xGridLines)
                     Toggle("Display Chart Y - Grid lines", isOn: $state.yGridLines)
                     Toggle("Display Chart Threshold lines for Low and High", isOn: $state.rulerMarks)

+ 1 - 1
FreeAPS/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift

@@ -39,7 +39,7 @@ extension TargetsEditor {
                 }
             }
             .onAppear(perform: configureView)
-            .navigationTitle("Target Ranges")
+            .navigationTitle("Target Glucose")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
                 trailing: EditButton()

+ 3 - 0
FreeAPS/Sources/Modules/WatchConfig/View/WatchConfigRootView.swift

@@ -18,6 +18,9 @@ extension WatchConfig {
                         }
                     }
                 }
+
+                Toggle("Display Protein & Fat", isOn: $state.displayFatAndProteinOnWatch)
+
                 Section(header: Text("Garmin Watch")) {
                     List {
                         ForEach(state.devices, id: \.uuid) { device in

+ 2 - 0
FreeAPS/Sources/Modules/WatchConfig/WatchConfigStateModel.swift

@@ -30,12 +30,14 @@ extension WatchConfig {
         @Injected() private var garmin: GarminManager!
         @Published var devices: [IQDevice] = []
         @Published var selectedAwConfig: AwConfig = .HR
+        @Published var displayFatAndProteinOnWatch = false
 
         private(set) var preferences = Preferences()
 
         override func subscribe() {
             preferences = provider.preferences
 
+            subscribeSetting(\.displayFatAndProteinOnWatch, on: $displayFatAndProteinOnWatch) { displayFatAndProteinOnWatch = $0 }
             subscribeSetting(\.displayOnWatch, on: $selectedAwConfig) { selectedAwConfig = $0 }
             didSet: { [weak self] value in
                 // for compatibility with old displayHR

+ 2 - 2
FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift

@@ -327,9 +327,9 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
 
         let query = HKObserverQuery(sampleType: bgType, predicate: nil) { [weak self] _, _, observerError in
             guard let self = self else { return }
-            debug(.service, "Execute HelathKit observer query for loading increment samples")
+            debug(.service, "Execute HealthKit observer query for loading increment samples")
             guard observerError == nil else {
-                warning(.service, "Error during execution of HelathKit Observer's query", error: observerError!)
+                warning(.service, "Error during execution of HealthKit Observer's query", error: observerError!)
                 return
             }
 

+ 2 - 0
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -392,6 +392,8 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                 insulin: nil,
                 notes: nil,
                 carbs: nil,
+                fat: nil,
+                protein: nil,
                 targetTop: nil,
                 targetBottom: nil
             )

+ 16 - 8
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -78,8 +78,10 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
             var insulinRequired = self.suggestion?.insulinReq ?? 0
             var double: Decimal = 2
             if (self.suggestion?.cob ?? 0) > 0 {
-                insulinRequired = self.suggestion?.insulinForManualBolus ?? 0
-                double = 1
+                if self.suggestion?.manualBolusErrorString == 0 {
+                    insulinRequired = self.suggestion?.insulinForManualBolus ?? 0
+                    double = 1
+                }
             }
 
             self.state.bolusRecommended = self.apsManager
@@ -102,8 +104,8 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
                     )
                 }
             self.state.bolusAfterCarbs = !self.settingsManager.settings.skipBolusScreenAfterCarbs
-
             self.state.displayOnWatch = self.settingsManager.settings.displayOnWatch
+            self.state.displayFatAndProteinOnWatch = self.settingsManager.settings.displayFatAndProteinOnWatch
 
             let eBG = self.evetualBGStraing()
             self.state.eventualBG = eBG.map { "⇢ " + $0 }
@@ -263,16 +265,22 @@ extension BaseWatchManager: WCSessionDelegate {
     func session(_: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
         debug(.service, "WCSession got message with reply handler: \(message)")
 
-        if let carbs = message["carbs"] as? Double, carbs > 0 {
-            carbsStorage.storeCarbs([
-                CarbsEntry(
+        if let carbs = message["carbs"] as? Double,
+           let fat = message["fat"] as? Double,
+           let protein = message["protein"] as? Double,
+           carbs > 0 || fat > 0 || protein > 0
+        {
+            carbsStorage.storeCarbs(
+                [CarbsEntry(
                     id: UUID().uuidString,
                     createdAt: Date(),
                     carbs: Decimal(carbs),
+                    fat: Decimal(fat),
+                    protein: Decimal(protein), note: nil,
                     enteredBy: CarbsEntry.manual,
                     isFPU: false, fpuID: nil
-                )
-            ])
+                )]
+            )
 
             if settingsManager.settings.skipBolusScreenAfterCarbs {
                 apsManager.determineBasalSync()

+ 1 - 1
FreeAPS/Sources/Views/TagCloudView.swift

@@ -59,7 +59,7 @@ struct TagCloudView: View {
                 return .uam
             case textTag where textTag.contains("Bolus"):
                 return .green
-            case textTag where textTag.contains("Total insulin:"),
+            case textTag where textTag.contains("TDD:"),
                  textTag where textTag.contains("tdd_factor"),
                  textTag where textTag.contains("Sigmoid function"),
                  textTag where textTag.contains("Logarithmic formula"),

+ 1 - 0
FreeAPSWatch WatchKit Extension/DataFlow.swift

@@ -21,6 +21,7 @@ struct WatchState: Codable {
     var eventualBG: String?
     var eventualBGRaw: String?
     var displayOnWatch: AwConfig?
+    var displayFatAndProteinOnWatch: Bool?
     var isf: Decimal?
     var override: String?
 }

+ 208 - 45
FreeAPSWatch WatchKit Extension/Views/CarbsView.swift

@@ -3,7 +3,19 @@ import SwiftUI
 struct CarbsView: View {
     @EnvironmentObject var state: WatchStateModel
 
-    @State var amount = 0.0
+    // Selected nutrient
+    enum Selection: String {
+        case carbs
+        case protein
+        case fat
+    }
+
+    @State var selection: Selection = .carbs
+    @State var carbAmount = 0.0
+    @State var fatAmount = 0.0
+    @State var proteinAmount = 0.0
+    @State var colorOfselection: Color = .darkGray
+    // @State var displayPresets: Bool = false
 
     var numberFormatter: NumberFormatter {
         let formatter = NumberFormatter()
@@ -16,62 +28,213 @@ struct CarbsView: View {
     }
 
     var body: some View {
-        GeometryReader { geo in
-            VStack(spacing: 16) {
-                HStack {
-                    Button {
-                        WKInterfaceDevice.current().play(.click)
-                        let newValue = amount - 5
-                        amount = max(newValue, 0)
-                    } label: {
+        VStack {
+            // nutrient
+            carbs
+            if state.displayFatAndProteinOnWatch {
+                Spacer()
+                fat
+                Spacer()
+                protein
+            }
+            buttonStack
+        }
+        .onAppear { carbAmount = Double(state.carbsRequired ?? 0) }
+    }
+
+    var nutrient: some View {
+        HStack {
+            switch selection {
+            case .protein:
+                Text("Protein")
+            case .fat:
+                Text("Fat")
+            default:
+                Text("Carbs")
+            }
+        }.font(.footnote).frame(maxWidth: .infinity, alignment: .center)
+    }
+
+    var carbs: some View {
+        HStack {
+            if selection == .carbs {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = carbAmount - 5
+                    carbAmount = max(newValue, 0)
+                }
+                label: {
+                    HStack {
                         Image(systemName: "minus")
+                        Text("") // Ugly fix to increase active tapping (button) area.
                     }
-                    .frame(width: geo.size.width / 4)
-                    Spacer()
-                    Text(numberFormatter.string(from: amount as NSNumber)! + " g")
-                        .font(.title2)
-                        .focusable(true)
-                        .digitalCrownRotation(
-                            $amount,
-                            from: 0,
-                            through: Double(state.maxCOB ?? 120),
-                            by: 1,
-                            sensitivity: .medium,
-                            isContinuous: false,
-                            isHapticFeedbackEnabled: true
-                        )
-                    Spacer()
-                    Button {
-                        WKInterfaceDevice.current().play(.click)
-                        let newValue = amount + 5
-                        amount = min(newValue, Double(state.maxCOB ?? 120))
-                    } label: { Image(systemName: "plus") }
-                        .frame(width: geo.size.width / 4)
                 }
+                .buttonStyle(.borderless).padding(.leading, 5)
+                .tint(selection == .carbs ? .blue : .none)
+            }
+            Spacer()
+            Text("🥨")
+            Spacer()
+            Text(numberFormatter.string(from: carbAmount as NSNumber)! + " g")
+                .font(selection == .carbs ? .title : .title3)
+                .focusable(selection == .carbs)
+                .digitalCrownRotation(
+                    $carbAmount,
+                    from: 0,
+                    through: Double(state.maxCOB ?? 120),
+                    by: 1,
+                    sensitivity: .medium,
+                    isContinuous: false,
+                    isHapticFeedbackEnabled: true
+                )
+            Spacer()
+            if selection == .carbs {
                 Button {
                     WKInterfaceDevice.current().play(.click)
-                    // Get amount from displayed string
-                    let amount = Int(numberFormatter.string(from: amount as NSNumber)!) ?? Int(amount.rounded())
-                    state.addCarbs(amount)
+                    let newValue = carbAmount + 5
+                    carbAmount = min(newValue, Double(state.maxCOB ?? 120))
+                } label: { Image(systemName: "plus") }
+                    .buttonStyle(.borderless).padding(.trailing, 5)
+                    .tint(selection == .carbs ? .blue : .none)
+            }
+        }
+        .minimumScaleFactor(0.7)
+        .onTapGesture {
+            select(entry: .carbs)
+        }
+        .background(selection == .carbs && state.displayFatAndProteinOnWatch ? colorOfselection : .black)
+        .padding(.top)
+    }
+
+    var protein: some View {
+        HStack {
+            if selection == .protein {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = proteinAmount - 5
+                    proteinAmount = max(newValue, 0)
+                } label: {
+                    HStack {
+                        Image(systemName: "minus")
+                        Text("") // Ugly fix to increase active tapping (button) area.
+                    }
                 }
-                label: {
+                .buttonStyle(.borderless).padding(.leading, 5)
+                .tint(selection == .protein ? .blue : .none)
+            }
+            Spacer()
+            Text("🍗")
+            Spacer()
+            Text(numberFormatter.string(from: proteinAmount as NSNumber)! + " g")
+                .font(selection == .protein ? .title : .title3)
+                .foregroundStyle(.red)
+                .focusable(selection == .protein)
+                .digitalCrownRotation(
+                    $proteinAmount,
+                    from: 0,
+                    through: Double(240),
+                    by: 1,
+                    sensitivity: .medium,
+                    isContinuous: false,
+                    isHapticFeedbackEnabled: true
+                )
+            Spacer()
+            if selection == .protein {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = proteinAmount + 5
+                    proteinAmount = min(newValue, Double(240))
+                } label: { Image(systemName: "plus") }.buttonStyle(.borderless).padding(.trailing, 5)
+                    .tint(selection == .protein ? .blue : .none)
+            }
+        }
+        .minimumScaleFactor(0.7)
+        .onTapGesture {
+            select(entry: .protein)
+        }
+        .background(selection == .protein ? colorOfselection : .black)
+    }
+
+    var fat: some View {
+        HStack {
+            if selection == .fat {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = fatAmount - 5
+                    fatAmount = max(newValue, 0)
+                } label: {
                     HStack {
-                        Image("carbs", bundle: nil)
-                            .renderingMode(.template)
-                            .resizable()
-                            .frame(width: 24, height: 24)
-                            .foregroundColor(.loopYellow)
-                        Text("Add Carbs ")
+                        Image(systemName: "minus")
+                        Text("") // Ugly fix to increase active tapping (button) area.
                     }
                 }
-                .disabled(amount <= 0)
-            }.frame(maxHeight: .infinity)
+                .buttonStyle(.borderless).padding(.leading, 5)
+                .tint(selection == .fat ? .blue : .none)
+            }
+            Spacer()
+            Text("🧀")
+            Spacer()
+            Text(numberFormatter.string(from: fatAmount as NSNumber)! + " g")
+                .font(selection == .fat ? .title : .title3)
+                .foregroundColor(.loopYellow)
+                .focusable(selection == .fat)
+                .digitalCrownRotation(
+                    $fatAmount,
+                    from: 0,
+                    through: Double(240),
+                    by: 1,
+                    sensitivity: .medium,
+                    isContinuous: false,
+                    isHapticFeedbackEnabled: true
+                )
+            Spacer()
+            if selection == .fat {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = fatAmount + 5
+                    fatAmount = min(newValue, Double(240))
+                } label: { Image(systemName: "plus") }
+                    .buttonStyle(.borderless).padding(.trailing, 5)
+                    .tint(selection == .fat ? .blue : .none)
+            }
         }
-        .navigationTitle("Add Carbs ")
+        .minimumScaleFactor(0.7)
+        .onTapGesture {
+            select(entry: .fat)
+        }
+        .background(selection == .fat ? colorOfselection : .black)
+    }
 
-        .onAppear {
-            amount = Double(state.carbsRequired ?? 0)
+    var buttonStack: some View {
+        HStack(spacing: 25) {
+            /* To do: display the actual meal presets
+             Button {
+                 displayPresets.toggle()
+             }
+             label: { Image(systemName: "menucard.fill") }
+                 .buttonStyle(.borderless)
+             */
+            Button {
+                WKInterfaceDevice.current().play(.click)
+                // Get amount from displayed string
+                let amountCarbs = Int(numberFormatter.string(from: carbAmount as NSNumber)!) ?? Int(carbAmount.rounded())
+                let amountFat = Int(numberFormatter.string(from: fatAmount as NSNumber)!) ?? Int(fatAmount.rounded())
+                let amountProtein = Int(numberFormatter.string(from: proteinAmount as NSNumber)!) ??
+                    Int(proteinAmount.rounded())
+                state.addMeal(amountCarbs, fat: amountFat, protein: amountProtein)
+            }
+            label: { Text("Save") }
+                .buttonStyle(.borderless)
+                .font(.callout)
+                .foregroundColor(carbAmount > 0 || fatAmount > 0 || proteinAmount > 0 ? .blue : .secondary)
+                .disabled(carbAmount <= 0 && fatAmount <= 0 && proteinAmount <= 0)
         }
+        .frame(maxHeight: .infinity, alignment: .bottom)
+        .padding(.top)
+    }
+
+    private func select(entry: Selection) {
+        selection = entry
     }
 }
 

+ 4 - 3
FreeAPSWatch WatchKit Extension/WatchStateModel.swift

@@ -33,6 +33,7 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var isTempTargetViewActive = false
     @Published var isBolusViewActive = false
     @Published var displayOnWatch: AwConfig = .BGTarget
+    @Published var displayFatAndProteinOnWatch = false
     @Published var eventualBG = ""
     @Published var isConfirmationViewActive = false {
         didSet {
@@ -53,7 +54,6 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var lastUpdate: Date = .distantPast
     @Published var timerDate = Date()
     @Published var pendingBolus: Double?
-
     @Published var isf: Decimal?
     @Published var override: String?
 
@@ -69,11 +69,11 @@ class WatchStateModel: NSObject, ObservableObject {
         session.activate()
     }
 
-    func addCarbs(_ carbs: Int) {
+    func addMeal(_ carbs: Int, fat: Int, protein: Int) {
         confirmationSuccess = nil
         isConfirmationViewActive = true
         isCarbsViewActive = false
-        session.sendMessage(["carbs": carbs], replyHandler: { reply in
+        session.sendMessage(["carbs": carbs, "fat": fat, "protein": protein], replyHandler: { reply in
             self.completionHandler(reply)
             if let ok = reply["confirmation"] as? Bool, ok, self.bolusAfterCarbs {
                 DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
@@ -173,6 +173,7 @@ class WatchStateModel: NSObject, ObservableObject {
         lastUpdate = Date()
         eventualBG = state.eventualBG ?? ""
         displayOnWatch = state.displayOnWatch ?? .BGTarget
+        displayFatAndProteinOnWatch = state.displayFatAndProteinOnWatch ?? false
         isf = state.isf
         override = state.override
     }

+ 3 - 2
README.md

@@ -20,14 +20,14 @@ Download and open in Xcode directly using the Code button: "Open with Xcode".
 
 ## To build directly in GitHub, without using Xcode: 
 
-Intructions:  
+Instructions:  
 https://github.com/Artificial-Pancreas/iAPS/blob/main/fastlane/testflight.md   
 Instructions in greater detail, but not iAPS-specific:  
 https://loopkit.github.io/loopdocs/gh-actions/gh-overview/
  
 ## Please understand that iAPS is:  
 - highly experimental and evolving rapidly.
-- not CE approved for therapy.
+- not CE or FDA approved for therapy.
 
 # Pumps
 
@@ -43,6 +43,7 @@ https://loopkit.github.io/loopdocs/gh-actions/gh-overview/
 
 - Dexcom G5  
 - Dexcom G6   
+- Dexcom ONE   
 - Dexcom G7   
 - Libre 1   
 - Libre 2 (European)