Pārlūkot izejas kodu

Release 2.1.6 (#95)

1. Use Loops data up to 90 days in stat view.
2. Fix Concurrency issue with CoreData Stat View.
3. Filter fetched requests dynamically.
4. Refactor SwiftUI code for Statistics.
5. Add Interval in minutes to LoopStatRecords to avoid looping.
6. Localize missing strings.
7. Crowdin translationsand new localizations.
8. add % of override in Apple Watch (Thanks Pierre Avous)
9. Format new Watch display options
10. Use a single target when scheduling target glucose. A fix for issue nr 28 https://github.com/Artificial-Pancreas/iAPS/issues/28
11. Change Sigmoid dynamic CR ratio to 100% of dynamic ISF ratio. A revert back to the original dynCR implementation.
12. Display scheduled basal dots. A UI bug fix for issue nr 60: https://github.com/Artificial-Pancreas/iAPS/issues/60
13.  Improve logging of dynamic CR
14. Add euglucemic range in Stat View.
15. Formatting of TIR Chart.
16. Check for empty loops. A bug fix. 
17. Bump version nr.
Jon B Mårtensson 2 gadi atpakaļ
vecāks
revīzija
f1fe381710
81 mainītis faili ar 2947 papildinājumiem un 1511 dzēšanām
  1. 1 1
      Config.xcconfig
  2. 1 0
      Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents
  3. 1 1
      Dependencies/CGMBLEKit/CGMBLEKit/nl.lproj/Localizable.strings
  4. 1 1
      Dependencies/CGMBLEKit/CGMBLEKit/zh-Hans.lproj/Localizable.strings
  5. 8 8
      Dependencies/CGMBLEKit/CGMBLEKitUI/nl.lproj/Localizable.strings
  6. 3 3
      Dependencies/CGMBLEKit/CGMBLEKitUI/nl.lproj/TransmitterManagerSetup.strings
  7. 2 2
      Dependencies/G7SensorKit/de.lproj/Localizable.strings
  8. 2 2
      Dependencies/G7SensorKit/nl.lproj/Localizable.strings
  9. 1 1
      Dependencies/MinimedKit/MinimedKit/Resources/de.lproj/Localizable.strings
  10. 7 7
      Dependencies/MinimedKit/MinimedKit/Resources/nl.lproj/Localizable.strings
  11. 2 2
      Dependencies/MinimedKit/MinimedKitUI/Resources/de.lproj/Localizable.strings
  12. 4 4
      Dependencies/MinimedKit/MinimedKitUI/Resources/it.lproj/Localizable.strings
  13. 21 21
      Dependencies/MinimedKit/MinimedKitUI/Resources/nl.lproj/Localizable.strings
  14. 1 1
      Dependencies/MinimedKit/MinimedKitUI/Resources/ru.lproj/Localizable.strings
  15. 39 39
      Dependencies/MinimedKit/MinimedKitUI/Resources/uk.lproj/Localizable.strings
  16. 4 1
      Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings
  17. 4 1
      Dependencies/OmniBLE/Localizations/da.lproj/Localizable.strings
  18. 4 1
      Dependencies/OmniBLE/Localizations/de.lproj/Localizable.strings
  19. 4 1
      Dependencies/OmniBLE/Localizations/en.lproj/Localizable.strings
  20. 4 1
      Dependencies/OmniBLE/Localizations/es.lproj/Localizable.strings
  21. 4 1
      Dependencies/OmniBLE/Localizations/fi.lproj/Localizable.strings
  22. 4 1
      Dependencies/OmniBLE/Localizations/fr.lproj/Localizable.strings
  23. 4 1
      Dependencies/OmniBLE/Localizations/he.lproj/Localizable.strings
  24. 22 19
      Dependencies/OmniBLE/Localizations/it.lproj/Localizable.strings
  25. 4 1
      Dependencies/OmniBLE/Localizations/nb.lproj/Localizable.strings
  26. 15 12
      Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings
  27. 4 1
      Dependencies/OmniBLE/Localizations/pl.lproj/Localizable.strings
  28. 4 1
      Dependencies/OmniBLE/Localizations/pt-BR.lproj/Localizable.strings
  29. 4 1
      Dependencies/OmniBLE/Localizations/pt-PT.lproj/Localizable.strings
  30. 4 1
      Dependencies/OmniBLE/Localizations/ru.lproj/Localizable.strings
  31. 4 1
      Dependencies/OmniBLE/Localizations/sk.lproj/Localizable.strings
  32. 4 1
      Dependencies/OmniBLE/Localizations/sv.lproj/Localizable.strings
  33. 4 1
      Dependencies/OmniBLE/Localizations/tr.lproj/Localizable.strings
  34. 4 1
      Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings
  35. 4 1
      Dependencies/OmniBLE/Localizations/zh-Hans.lproj/Localizable.strings
  36. 3 0
      Dependencies/OmniBLE/OmniBLE/en.lproj/Localizable.strings
  37. 22 22
      Dependencies/OmniKit/OmniKit/Resources/nl.lproj/Localizable.strings
  38. 1 1
      Dependencies/OmniKit/OmniKitUI/Resources/de.lproj/Localizable.strings
  39. 68 68
      Dependencies/OmniKit/OmniKitUI/Resources/nl.lproj/Localizable.strings
  40. 13 13
      Dependencies/rileylink_ios/RileyLinkKitUI/nl.lproj/Localizable.strings
  41. 2 2
      Dependencies/rileylink_ios/RileyLinkKitUI/ru.lproj/Localizable.strings
  42. 12 0
      FreeAPS.xcodeproj/project.pbxproj
  43. 2 1
      FreeAPS/Resources/javascript/bundle/determine-basal.js
  44. 50 91
      FreeAPS/Sources/APS/APSManager.swift
  45. 67 1
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  46. 63 0
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  47. 67 1
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  48. 109 43
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  49. 64 5
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  50. 67 1
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  51. 67 1
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  52. 67 1
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  53. 67 1
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  54. 326 259
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  55. 67 1
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  56. 137 71
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  57. 67 1
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  58. 67 1
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  59. 67 1
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  60. 67 1
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  61. 67 1
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  62. 69 3
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  63. 67 1
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  64. 67 1
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  65. 67 1
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  66. 10 0
      FreeAPS/Sources/Models/DateFilter.swift
  67. 5 1
      FreeAPS/Sources/Models/LoopStats.swift
  68. 3 2
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  69. 15 4
      FreeAPS/Sources/Modules/OverrideProfilesConfig/View/OverrideProfilesRootView.swift
  70. 5 6
      FreeAPS/Sources/Modules/Stat/StatStateModel.swift
  71. 324 0
      FreeAPS/Sources/Modules/Stat/View/ChartsView.swift
  72. 93 738
      FreeAPS/Sources/Modules/Stat/View/StatRootView.swift
  73. 299 0
      FreeAPS/Sources/Modules/Stat/View/StatsView.swift
  74. 2 2
      FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorDataFlow.swift
  75. 3 3
      FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift
  76. 1 17
      FreeAPS/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift
  77. 3 0
      FreeAPS/Sources/Modules/WatchConfig/WatchConfigStateModel.swift
  78. 18 0
      FreeAPS/Sources/Services/WatchManager/WatchManager.swift
  79. 1 0
      FreeAPSWatch WatchKit Extension/DataFlow.swift
  80. 13 2
      FreeAPSWatch WatchKit Extension/Views/MainView.swift
  81. 3 0
      FreeAPSWatch WatchKit Extension/WatchStateModel.swift

+ 1 - 1
Config.xcconfig

@@ -1,5 +1,5 @@
 APP_DISPLAY_NAME = iAPS
-APP_VERSION = 2.0.0
+APP_VERSION = 2.1.6
 APP_BUILD_NUMBER = 1
 COPYRIGHT_NOTICE =
 DEVELOPER_TEAM = ##TEAM_ID##

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

@@ -36,6 +36,7 @@
     <entity name="LoopStatRecord" representedClassName="LoopStatRecord" syncable="YES" codeGenerationType="class">
         <attribute name="duration" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
         <attribute name="end" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="interval" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
         <attribute name="loopStatus" optional="YES" attributeType="String"/>
         <attribute name="start" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
     </entity>

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

@@ -8,7 +8,7 @@
 "Glucose data is unavailable" = "Glucosegegevens zijn niet beschikbaar";
 
 /* Describes a low battery */
-"Low Battery" = "Batterij Bijna Leeg";
+"Low Battery" = "Batterij bijna leeg";
 
 /* Describes a functioning transmitter */
 "OK" = "Ok";

+ 1 - 1
Dependencies/CGMBLEKit/CGMBLEKit/zh-Hans.lproj/Localizable.strings

@@ -1,5 +1,5 @@
 /* CGM display title */
-"Dexcom G5" = "Dexcom G5";
+"Dexcom G5" = "Dexcom G 5";
 
 /* CGM display title */
 "Dexcom G6" = "Dexcom G6";

+ 8 - 8
Dependencies/CGMBLEKit/CGMBLEKitUI/nl.lproj/Localizable.strings

@@ -21,28 +21,28 @@ Title text for the button to remove a CGM from Loop */
 "Glucose (Adjusted)" = "Glucose (Aangepast)";
 
 /* Section title for latest glucose calibration */
-"Latest Calibration" = "Laatste Kalibratie";
+"Latest Calibration" = "Laatste kalibratie";
 
 /* Section title for latest glucose reading */
-"Latest Reading" = "Laatste Meting";
+"Latest Reading" = "Laatste meting";
 
 /* Section title for latest connection date */
-"Latest Connection" = "Laatste Verbinding";
+"Latest Connection" = "Laatste verbinding";
 
 /* Button title to open CGM app */
-"Open App" = "Open App";
+"Open App" = "App openen";
 
 /* Title describing sensor session age */
 "Session Age" = "Sessieduur";
 
 /* Section title for remote data synchronization */
-"Remote Data Synchronization" = "Remote Gegevenssynchronisatie";
+"Remote Data Synchronization" = "Synchronisatie van gegevens op afstand";
 
 /* Title describing sensor expiration */
-"Sensor Expires" = "Sensor Verloopt";
+"Sensor Expires" = "Sensor verloopt";
 
 /* Title describing past sensor expiration */
-"Sensor Expired" = "Sensor Verlopen";
+"Sensor Expired" = "Sensor verlopen";
 
 /* Title describing CGM calibration and battery state */
 "Status" = "Status";
@@ -57,4 +57,4 @@ Title text for the button to remove a CGM from Loop */
 "Trend" = "Trend";
 
 /* The title text for the upload glucose switch cell */
-"Upload Readings" = "Upload Metingen";
+"Upload Readings" = "Upload metingen";

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

@@ -8,10 +8,10 @@
 "GOT-KQ-cEh.text" = "Detail";
 
 /* Class = "UITableViewSection"; footerTitle = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app."; ObjectID = "Qub-6B-0aB"; */
-"Qub-6B-0aB.footerTitle" = "De zenderserienummer staat achter op het apparaat, op de zijkant van de verpakking en in het instellingenmenu van de ontvanger en de app.";
+"Qub-6B-0aB.footerTitle" = "Het serienummer van de zender staat achter op het apparaat, op de zijkant van de verpakking en in het instellingenmenu van de ontvanger en de app.";
 
 /* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */
-"Qub-6B-0aB.headerTitle" = "Zenderserienummer";
+"Qub-6B-0aB.headerTitle" = "Serienummer van de zender";
 
 /* Class = "UITableViewSection"; footerTitle = "Data can be downloaded over the Internet from Share when the transmitter connection fails."; ObjectID = "k1N-Rg-XDy"; */
 "k1N-Rg-XDy.footerTitle" = "Gegevens kunnen via het internet van Share gedownload worden wanneer de verbinding met de zender uitvalt.";
@@ -20,4 +20,4 @@
 "k1N-Rg-XDy.headerTitle" = "Dexcom Share";
 
 /* Class = "UITextField"; placeholder = "Enter the 6-digit transmitter ID"; ObjectID = "nKX-TW-GhD"; */
-"nKX-TW-GhD.placeholder" = "Vul de 6 cijferige zenderserienummer in";
+"nKX-TW-GhD.placeholder" = "Vul het 6 cijferige serienummer van de zender in";

+ 2 - 2
Dependencies/G7SensorKit/de.lproj/Localizable.strings

@@ -38,7 +38,7 @@
 "Sensor Expiration" = "Sensor Ablaufdatum";
 
 /* title for g7 settings row showing sensor grace period end time */
-"Grace Period End" = "Grace Period End";
+"Grace Period End" = "Ende der Karenzfrist";
 
 /* Field label */
 "Glucose" = "Blutzucker";
@@ -108,7 +108,7 @@
 "Sensor expires" = "Sensor abgelaufen";
 
 /* G7 Progress bar label when sensor grace period progress showing */
-"Grace period remaining" = "Grace period remaining";
+"Grace period remaining" = "Verbleibende Karenzfrist";
 
 /* G7 Status highlight text for searching for sensor */
 "Searching for\nSensor" = "Suche nach\nSensor";

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

@@ -14,7 +14,7 @@
 "Glucose data is unavailable" = "Glucose gegevens niet beschikbaar";
 
 /* The description of sensor algorithm state when sensor is ok. */
-"Sensor is OK" = "Sensor is OK";
+"Sensor is OK" = "Sensor is ok";
 
 /* The description of sensor algorithm state when sensor is stopped." */
 "Sensor is stopped" = "Sensor is gestopt";
@@ -49,7 +49,7 @@
 
 "Trend" = "Trend";
 
-"Bluetooth" = "Bluetooth";
+"Bluetooth" = "Bluethooth";
 
 /* title for g7 settings row showing BLE Name */
 "Name" = "Naam";

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

@@ -2,7 +2,7 @@
 "A bolus is already in progress" = "Ein Bolus wird bereits abgegeben";
 
 /* The description of AlarmClockReminderPumpEvent */
-"AlarmClockReminder" = "AlarmClockReminder";
+"AlarmClockReminder" = "Alarm Erinnerung";
 
 /* The description of AlarmSensorPumpEvent */
 "AlarmSensor" = "AlarmSensor";

+ 7 - 7
Dependencies/MinimedKit/MinimedKit/Resources/nl.lproj/Localizable.strings

@@ -2,10 +2,10 @@
 "A bolus is already in progress" = "Er is al een bolus actief";
 
 /* The description of AlarmClockReminderPumpEvent */
-"AlarmClockReminder" = "AlarmClockReminder";
+"AlarmClockReminder" = "Wekker herinnering";
 
 /* The description of AlarmSensorPumpEvent */
-"AlarmSensor" = "AlarmSensor";
+"AlarmSensor" = "Alarm sensor";
 
 /* Describing the battery chemistry as Alkaline */
 "Alkaline" = "Alkaline";
@@ -17,7 +17,7 @@
 "Bolus in progress" = "Bolus in uitvoering";
 
 /* Suggestions for diagnosing a command refused pump error */
-"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Controleer dat de pomp niet is onderbroken of aan het voorbereiden is, of dat er een percentage tijdelijke basaal staat ingesteld";
+"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Controleer of de pomp niet is onderbroken of aan het voorbereiden is, of dat er een percentage tijdelijke basaal staat ingesteld";
 
 /* Pump error code returned when command refused */
 "Command refused" = "Commando geweigerd";
@@ -41,7 +41,7 @@
 "Lithium" = "Lithium";
 
 /* Recovery suggestion */
-"Make sure your RileyLink is nearby and powered on" = "Zorg ervoor dat je RileyLink dichtbij is en aan staat";
+"Make sure your RileyLink is nearby and powered on" = "Zorg ervoor dat je RileyLink dicht bij is en aan staat";
 
 /* Pump error code describing max setting exceeded */
 "Max setting exceeded" = "Max instelling overschreden";
@@ -59,7 +59,7 @@
 "Pump did not respond" = "Pomp reageert niet";
 
 /* Error description */
-"Pump Error" = "Pomp Foutmelding";
+"Pump Error" = "Pomp foutmelding";
 
 /* No comment provided by engineer. */
 "Pump is suspended" = "Pomp is onderbroken";
@@ -68,13 +68,13 @@
 "Pump responded unexpectedly" = "Pomp reageerde onverwacht";
 
 /* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */
-"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "Bericht Pomp (%1$@, %2$@, %3$@, %4$@)";
+"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "Bericht pomp (%1$@, %2$@, %3$@, %4$@)";
 
 /* Describing the reservoir insulin data source */
 "Reservoir" = "Reservoir";
 
 /* Error description */
-"RileyLink radio tune failed" = "Afstemmen van de RileyLink Radio mislukt";
+"RileyLink radio tune failed" = "Afstemmen van de RileyLink frequentie mislukt";
 
 /* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */
 "Temporary Basal: %1$.3f U/hour" = "Tijdelijk basaal: %1$.3f E/uur";

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

@@ -35,7 +35,7 @@
 "Best Frequency" = "Beste Frequenz";
 
 /* The format string describing pump bolusing state: (1: bolusing) */
-"Bolusing: %1$@\n" = "Bolusing: %1$@\n";
+"Bolusing: %1$@\n" = "Bolusabgabe: %1$@\n";
 
 /* Cancel button title */
 "Cancel" = "Abbrechen";
@@ -123,7 +123,7 @@
 "No, Keep Pump As Is" = "Nein, Pumpe so lassen wie sie ist";
 
 /* 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" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device";
+"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "Gehen Sie auf Ihrer Pumpe zum Menü \"Geräte anschließen\" und wählen Sie „Geräte finden\".\n\nHauptmenü Insulinpumpe >\nZusatzfunktionen >\nGeräte anschließen >\nAndere Geräte >\nEin >\nGerät finden";
 
 /* navigation title for pump battery type selection
    Text for medtronic pump preferred data source */

+ 4 - 4
Dependencies/MinimedKit/MinimedKitUI/Resources/it.lproj/Localizable.strings

@@ -123,14 +123,14 @@
 "No, Keep Pump As Is" = "No, mantienere la pompa così com'è";
 
 /* 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" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device";
+"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "Sulla pompa, vai alla schermata Trova dispositivo e seleziona \"Trova dispositivo\".\n\nMenu principale >\nUtilities >\nConnetti Dispositivi >\nAltri Dispositivi >\nSu >\nTrova Dispositivi";
 
 /* navigation title for pump battery type selection
    Text for medtronic pump preferred data source */
 "Preferred Data Source" = "Fonte Dati";
 
 /* Text for medtronic pump battery percent remaining */
-"Pump Battery Remaining" = "Pump Battery Remaining";
+"Pump Battery Remaining" = "Batteria Pompa Rimanente";
 
 /* navigation title for pump battery type selection
    Text for medtronic pump battery type */
@@ -149,7 +149,7 @@
 "Reading pump status…" = "Lettura stato microinfusore…";
 
 /* The title of the cell showing the pump region */
-"Region" = "Region";
+"Region" = "Paese";
 
 /* Title text for button to resume insulin delivery */
 "Resume Delivery" = "Riprendi erogazione";
@@ -203,7 +203,7 @@
 "U/hr" = "U/ora";
 
 /* Text to indicate battery percentage is unknown */
-"unknown" = "unknown";
+"unknown" = "sconosciuto";
 
 /* Text shown in basal rate space when delivery status is unknown */
 "Unknown" = "Sconosciuto";

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 21 - 21
Dependencies/MinimedKit/MinimedKitUI/Resources/nl.lproj/Localizable.strings


+ 1 - 1
Dependencies/MinimedKit/MinimedKitUI/Resources/ru.lproj/Localizable.strings

@@ -93,7 +93,7 @@
 "Firmware Version" = "Версия прошивки";
 
 /* Text shown in insulin delivery space when insulin suspended */
-"Insulin\nSuspended" = "Подача инсулина остановлена";
+"Insulin\nSuspended" = "Подача\nприостановлена";
 
 /* Title of insulin delivery section */
 "Insulin Delivery" = "Подача инсулина";

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 39 - 39
Dependencies/MinimedKit/MinimedKitUI/Resources/uk.lproj/Localizable.strings


+ 4 - 1
Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ ago";

+ 4 - 1
Dependencies/OmniBLE/Localizations/da.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ siden";

+ 4 - 1
Dependencies/OmniBLE/Localizations/de.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Kritische Warnungen";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "Die obigen Hinweise ertönen nicht, wenn sich dein Gerät im Modus Lautlos oder Nicht stören befindet.\n\n Es gibt weitere kritische Pod-Warnungen und Alarme, die auch dann ertönen, wenn dein Gerät auf \"Lautlos\" oder \"Nicht stören\" eingestellt ist.";
+"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." = "Die obigen Hinweise ertönen nicht, wenn sich dein Gerät im Modus Lautlos oder Nicht stören befindet.\n\n Es gibt weitere kritische Pod-Warnungen und Alarme, die auch dann ertönen, wenn dein Gerät auf \"Lautlos\" oder \"Nicht stören\" eingestellt ist.";
 /* navigation title for notification settings */
 "Notification Settings" = "Benachrichtigungseinstellungen";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Warte, bis die aktuelle temporäre Basalrate beendet wurde, oder brich sie ab.";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ vor";

+ 4 - 1
Dependencies/OmniBLE/Localizations/en.lproj/Localizable.strings

@@ -573,7 +573,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
     
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
@@ -787,3 +787,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ ago";

+ 4 - 1
Dependencies/OmniBLE/Localizations/es.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "Hace %@";

+ 4 - 1
Dependencies/OmniBLE/Localizations/fi.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@";

+ 4 - 1
Dependencies/OmniBLE/Localizations/fr.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Alertes critiques";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "Les rappels ci-dessus ne sonneront pas si votre appareil est en mode silencieux ou Ne pas déranger.\n\nIl y a d'autres alertes et alarmes de Pod critiques qui sonneront même si votre appareil est réglé sur mode Silencieux ou Ne pas déranger.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Paramètres des notifications";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "Il y a %@";

+ 4 - 1
Dependencies/OmniBLE/Localizations/he.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ ago";

+ 22 - 19
Dependencies/OmniBLE/Localizations/it.lproj/Localizable.strings

@@ -451,13 +451,13 @@
 "If you cancel Pod setup, the current Pod will be deactivated and will be unusable." = "Se si annulla la configurazione del Pod, il Pod corrente verrà disattivato e sarà inutilizzabile.";
 
 /* Button title for confirm deactivation option */
-"Yes, Deactivate Pod" = "Sí, Disartiva Pod";
+"Yes, Deactivate Pod" = "Sì, disattiva Pod";
 
 /* Continue pairing button title of in pairing cancel modal */
 "No, Continue With Pod" = "No, continua con il Pod";
 
 /* Label text for step one of attach pod instructions */
-"Prepare site." = "Presaprà il sito.";
+"Prepare site." = "Prepara il sito.";
 
 /* Label text for step two of attach pod instructions */
 "Remove blue Pod needle cap and check cannula. Then remove paper backing." = "Rimuovere il cappuccio blu dell’ago e controlla la cannula. Quindi rimuovi il supporto cartaceo.";
@@ -571,7 +571,7 @@
 "Critical Alerts" = "Avvisi critici";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "I promemoria qui sopra non suoneranno se il dispositivo è in modalità Silenzioso o Non Disturbare.\n\nCi sono altri avvisi e avvisi di Pod critici che suoneranno anche se il dispositivo è impostato in modalità Silenzioso o Non Disturbare.";
+"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." = "I promemoria qui sopra non suoneranno se il dispositivo è in modalità silenzioso o non disturbare.\n\nCi sono altri avvisi e avvisi di pod critici che suoneranno anche se il dispositivo è impostato in modalità silenzioso o non disturbare.";
 /* navigation title for notification settings */
 "Notification Settings" = "Impostazioni di Notifica";
 
@@ -611,7 +611,7 @@
 "Rate" = "Valore";
 
 /* Insulin unit per hour */
-"U/hr" = "Unità/ora";
+"U/hr" = "U/ora";
 
 /* Summary string for temporary basal rate configuration page */
 "%1$@ for %2$@" = "%1$@ per %2$@";
@@ -685,7 +685,7 @@
 "Pod Setup" = "Configurazione pod";
 
 /* bodyText for PodSetupView */
-"You will now begin the process of configuring your reminders, filling your Pod with insulin, pairing to your device and placing it on your body." = "You will now begin the process of configuring your reminders, filling your Pod with insulin, pairing to your device and placing it on your body.";
+"You will now begin the process of configuring your reminders, filling your Pod with insulin, pairing to your device and placing it on your body." = "Ora inizierai il processo di configurazione dei tuoi promemoria, riempiendo il tuo Pod d'insulina, accoppiandolo al tuo dispositivo e posizionandolo sul tuo corpo.";
 
 /* Cancel button title */
 "Cancel" = "Cancella";
@@ -697,7 +697,7 @@
 "Skip Omnipod Onboarding?" = "Saltare la procedura di installazione di Omnipod?";
 
 /* 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." = "The App notifies you in advance of Pod expiration.\n\nScroll to set the number of hours advance notice you would like to have.";
+"The App notifies you in advance of Pod expiration.\n\nScroll to set the number of hours advance notice you would like to have." = "L'app ti informa in anticipo della scadenza del Pod.\n\nScorri per impostare il numero di ore di preavviso che vorresti avere.";
 
 /* Text of continue button on ExpirationReminderSetupView" */
 "Next" = "Avanti";
@@ -706,7 +706,7 @@
 "Expiration Reminder" = "Promemoria di scadenza";
 
 /* 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." = "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.";
+"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." = "L' app ti avvisa quando la quantità d'insulina nel Pod raggiunge questo livello (50-10 U).\n\nScorri per impostare il numero di unità da ricordare.";
 
 /* Label text for low reservoir value row */
 "Low Reservoir" = "Livello serbatoio basso";
@@ -715,13 +715,13 @@
 "Save" = "Salva";
 
 /* hr (short for hour) */
-"hr" = "hr";
+"hr" = "h";
 
 /* Button title to cancel manual basal */
 "Cancel Manual Basal" = "Annulla basale manuale";
 
 /* Text shown in insulin delivery space when insulin suspended */
-"Insulin\nSuspended" = "Insulin\nSuspended";
+"Insulin\nSuspended" = "Insulina\nSospesa";
 
 /* Text for suspend resume button when insulin delivery is suspended */
 "Resume Insulin Delivery" = "Riprendi erogazione insulina";
@@ -748,37 +748,40 @@
 "Too many pods found" = "Trovati troppi pod";
 
 /* Recovery suggestion when no response is received from pod */
-"Make sure iPhone is nearby the active pod" = "Make sure iPhone is nearby the active pod";
+"Make sure iPhone is nearby the active pod" = "Assicurarsi che iPhone sia vicino al Pod attivo";
 
 /* Recovery suggestion when ack received instead of response */
 "Try again" = "Riprovare";
 
 /* Recovery suggestion for PodCommsError.tooManyPodsFound */
-"Move to a new area away from any other pods and try again." = "Move to a new area away from any other pods and try again.";
+"Move to a new area away from any other pods and try again." = "Spostati in una nuova area lontana da qualsiasi altro Pod e riprova.";
 
 /* Recovery suggestion for PodCommsError.noPodsFound */
-"Make sure your pod is filled and nearby." = "Make sure your pod is filled and nearby.";
+"Make sure your pod is filled and nearby." = "Assicurati che il Pod sia riempito e nelle vicinanze.";
 
 /* Recovery suggestion when pairing signal strength is too high */
-"Please reposition iPhone further from the pod" = "Please reposition iPhone further from the pod";
+"Please reposition iPhone further from the pod" = "Si prega di riposizionare iPhone più lontano dal Pod";
 
 /* Recovery suggestion when pairing signal strength is too low */
-"Please reposition iPhone relative to the pod" = "Please reposition iPhone relative to the pod";
+"Please reposition iPhone relative to the pod" = "Si prega di riposizionare l'iPhone rispetto al Pod";
 
 /* Recovery suggestion on unexpected pod change */
-"Please bring only original pod in range or deactivate original pod" = "Please bring only original pod in range or deactivate original pod";
+"Please bring only original pod in range or deactivate original pod" = "Si prega di portare solo il pod originale nel raggio d'azione o disattivare il pod originale";
 
 /* Recovery suggestion when unexpected address received */
-"Crosstalk possible. Please move to a new location" = "Crosstalk possible. Please move to a new location";
+"Crosstalk possible. Please move to a new location" = "Possibile dialogo incrociato. Si prega di spostarsi in una nuova posizione";
 
 /* Recovery suggestion when no pod is available */
 "Make sure your pod is nearby and try again." = "Assicurati che il tuo pod sia vicino e riprova.";
 
 /* Recovery suggestion when operation could not be completed due to existing bolus in progress */
-"Wait for existing bolus to finish, or cancel bolus" = "Wait for existing bolus to finish, or cancel bolus";
+"Wait for existing bolus to finish, or cancel bolus" = "Attendere la fine del bolo o annulla il bolo";
 
 /* Recovery suggestion when operation could not be completed due to existing bolus in progress */
-"Wait for existing bolus to finish, or cancel bolus" = "Wait for existing bolus to finish, or cancel bolus";
+"Wait for existing bolus to finish, or cancel bolus" = "Attendere la fine del bolo o annulla il bolo";
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
-"Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+"Wait for existing temp basal to finish, or suspend to cancel" = "Attendi il termine della velocità basale temporanea esistente oppure sospendi per annullare";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ fa";

+ 4 - 1
Dependencies/OmniBLE/Localizations/nb.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Kritiske varslinger";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "Påminnelsene over vil ikke lyde hvis enheten er i stillemodus eller Ikke forstyrr-modus.\n\nDet finnes andre kritiske pod-varsler og -alarmer som vil lyde selv om enheten er satt til stillemodus eller Ikke forstyrr.";
+"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." = "Påminnelsene over vil ikke lyde hvis enheten er i stillemodus eller Ikke forstyrr-modus.\n\nDet finnes andre kritiske pod-varsler og -alarmer som vil lyde selv om enheten er satt til stillemodus eller Ikke forstyrr.";
 /* navigation title for notification settings */
 "Notification Settings" = "Varslingsinnstillinger";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Vent til eksisterende midlertidig basal er ferdig, eller sett insulintilførsel på pause for å avbryte.";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ siden";

+ 15 - 12
Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings

@@ -93,10 +93,10 @@
 "No Pod" = "Geen Pod";
 
 /* Settings page link description when next lifecycle action is to pair new pod */
-"Pair Pod" = "Koppel pod";
+"Pair Pod" = "Koppel Pod";
 
 /* Pairing action button accessibility label while ready to pair */
-"Pair pod." = "Koppel pod.";
+"Pair pod." = "Koppel Pod.";
 
 /* Pairing action button accessibility label while pairing */
 "Pairing." = "Verbinden….";
@@ -111,7 +111,7 @@
 "Finish deactivation" = "Voltooi de deactivering";
 
 /* Settings page link description when next lifecycle action is to replace pod */
-"Replace Pod" = "Vervang pod";
+"Replace Pod" = "Vervang Pod";
 
 /* Unit for singular day in pod life remaining */
 "day" = "dag";
@@ -153,10 +153,10 @@
 "Finish deactivation" = "Voltooi de deactivering";
 
 /* Settings page link description when next lifecycle action is to replace pod */
-"Replace Pod" = "Vervang pod";
+"Replace Pod" = "Vervang Pod";
 
 /* Settings page link description when next lifecycle action is to replace pod */
-"Replace Pod" = "Vervang pod";
+"Replace Pod" = "Vervang Pod";
 
 /* Label for pod life state when pod not fully activated */
 "Unfinished Activation" = "Onvoltooide activering";
@@ -183,10 +183,10 @@
 "Remaining" = "Resterend";
 
 /* Label indicating pod replacement necessary */
-"Replace Pod" = "Vervang pod";
+"Replace Pod" = "Vervang Pod";
 
 /* Error message shown when no pod is paired */
-"No pod paired" = "Geen pod verbonden";
+"No pod paired" = "Geen Pod verbonden";
 
 /* Error message shown when user cannot pair because pod is already paired */
 "Pod already paired" = "Pod reeds verbonden";
@@ -201,7 +201,7 @@
 "Invalid Setting" = "Ongeldige instelling";
 
 /* Recovery suggestion shown when no pod is paired */
-"Please pair a new pod" = "Verbind een nieuwe pod";
+"Please pair a new pod" = "Verbind een nieuwe Pod";
 
 /* Generic title of the OmniBLE pump manager */
 "Omnipod DASH" = "Omnipod DASH";
@@ -348,7 +348,7 @@
 "Yes, Sync to Current Time" = "Ja, zet naar huidige tijd";
 
 /* Button text to cancel pump time sync */
-"No, Keep Pump As Is" = "Nee, Pomp laten zoals hij is";
+"No, Keep Pump As Is" = "Nee, pomp laten zoals hij is";
 
 /* Title for Omnipod DASH PumpManager deletion action sheet. */
 "Remove Pump" = "Verwijder Pod";
@@ -400,7 +400,7 @@
 "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.";
 
 /* Deactivate pod action button */
-"Deactivate Pod" = "Deactiveer pod";
+"Deactivate Pod" = "Deactiveer Pod";
 
 /* Deactivate pod action button accessibility label while deactivating */
 "Deactivating." = "Deactiveren";
@@ -571,7 +571,7 @@
 "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 you device is set to Silent or Do Not Disturb mode." = "De bovenstaande herinneringen waarschuwen zonder geluid als uw apparaat in de modus Stil of Niet storen staat.\n\nEr zijn nog andere belangrijke Pod waarschuwingen en -alarmen die 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 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.";
 /* navigation title for notification settings */
 "Notification Settings" = "Instellingen voor meldingen";
 
@@ -585,7 +585,7 @@
 "Low Reservoir Reminder" = "Laag reservoir herinnering";
 
 /* The action string on pod status page when pod data is stale */
-"Make sure your phone and pod are close to each other. If communication issues persist, move to a new area." = "Zorg ervoor dat je telefoon en pod dicht bij elkaar liggen. Als communicatieproblemen aanhouden, ga dan naar een nieuw gebied.";
+"Make sure your phone and pod are close to each other. If communication issues persist, move to a new area." = "Zorg ervoor dat je iPhone en Pod dicht bij elkaar liggen. Als communicatieproblemen aanhouden, ga dan naar een nieuw gebied.";
 /* Format string for the action string on pod status page when pod expired. (1: service time remaining) */
 "Change Pod now. Insulin delivery will stop in %1$@ or when no more insulin remains." = "Verander Pod nu. Insuline levering stopt over %1$@ of wanneer er geen insuline meer over is.";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wacht op huidig tijdelijk basaal of onderbreek om te annuleren";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ geleden";

+ 4 - 1
Dependencies/OmniBLE/Localizations/pl.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ temu";

+ 4 - 1
Dependencies/OmniBLE/Localizations/pt-BR.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ atrás";

+ 4 - 1
Dependencies/OmniBLE/Localizations/pt-PT.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ ago";

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 4 - 1
Dependencies/OmniBLE/Localizations/ru.lproj/Localizable.strings


+ 4 - 1
Dependencies/OmniBLE/Localizations/sk.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notification Settings";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "pred %@";

+ 4 - 1
Dependencies/OmniBLE/Localizations/sv.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Varningar";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "Påminnelser kommer vara tysta ifall att du har tyst läge eller stör ej - läge på. \n\nDet finns andra varningar som kommer att ljuda även vid tyst läge leer stör ej - läge.";
+"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." = "Påminnelser kommer vara tysta ifall att du har tyst läge eller stör ej - läge på. \n\nDet finns andra varningar som kommer att ljuda även vid tyst läge leer stör ej - läge.";
 /* navigation title for notification settings */
 "Notification Settings" = "Notisinställningar";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Vänta på pågående temp. basal är färdig, alternativt pausa eller avbryt";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ sedan";

+ 4 - 1
Dependencies/OmniBLE/Localizations/tr.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Kritik Uyarılar";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "Cihazınız Sessiz veya Rahatsız Etmeyin modundaysa yukarıdaki hatırlatıcılar çalmaz.\n\nCihazınız Sessiz veya Rahatsız Etmeyin moduna ayarlanmış olsa bile çalacak başka kritik Pod uyarıları ve alarmları vardır.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "Bildirim ayarları";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Mevcut geçici bazalın bitmesini bekleyin veya askıya almak için iptal edin";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ önce";

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 4 - 1
Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings


+ 4 - 1
Dependencies/OmniBLE/Localizations/zh-Hans.lproj/Localizable.strings

@@ -571,7 +571,7 @@
 "Critical Alerts" = "Critical Alerts";
 
 /* 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 you device is set to Silent or Do Not Disturb mode." = "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 you device is set to Silent or Do Not Disturb mode.";
+"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." = "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 you device is set to Silent or Do Not Disturb mode.";
 /* navigation title for notification settings */
 "Notification Settings" = "通知设置";
 
@@ -782,3 +782,6 @@
 
 /* Recovery suggestion when operation could not be completed due to existing temp basal in progress */
 "Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ ago";

+ 3 - 0
Dependencies/OmniBLE/OmniBLE/en.lproj/Localizable.strings

@@ -176,3 +176,6 @@
 
 /* Description waiting for pairing reminder */
 "Waiting for pairing reminder" = "Waiting for pairing reminder";
+
+/* DASH Pod time ago since last status */
+"%@ ago" = "%@ ago";

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

@@ -72,7 +72,7 @@
 "Critical Pod Error" = "Kritieke Podfout";
 
 /* Recovery suggestion when unexpected address received */
-"Crosstalk possible. Please move to a new location" = "Overspraak mogelijk. Probeer een andere locatie";
+"Crosstalk possible. Please move to a new location" = "Ruis mogelijk. Verplaats alsjeblieft naar een andere locatie";
 
 /* Pod state when pod has been deactivated */
 "Deactivated" = "Gedeactiveerd";
@@ -105,10 +105,10 @@
 "Fault event occurred" = "Fout is opgetreden";
 
 /* Status highlight that when pod is deactivating. */
-"Finish Deactivation" = "Voltooi Deactivering";
+"Finish Deactivation" = "Voltooi deactivering";
 
 /* Status highlight that when pod is activating. */
-"Finish Pairing" = "Voltooi Koppelen";
+"Finish Pairing" = "Voltooi koppelen";
 
 /* Description for finish setup */
 "Finish setup " = "Voltooi installatie";
@@ -123,10 +123,10 @@
 "Inserting cannula" = "Canule inbrengen";
 
 /* The default notification body for AlarmCodes */
-"Insulin delivery stopped. Change Pod now." = "Insulinetoediening gestopt. Vervang Pod nu.";
+"Insulin delivery stopped. Change Pod now." = "Insulinetoediening gestopt. Vervang pod nu.";
 
 /* Status highlight that insulin delivery was suspended. */
-"Insulin Suspended" = "Insuline Onderbroken";
+"Insulin Suspended" = "Insuline onderbroken";
 
 /* Error description for OmniBLEPumpManagerError.insulinTypeNotConfigured */
 "Insulin type not configured" = "Insulinetype niet ingesteld";
@@ -153,19 +153,19 @@
 "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)";
+"Low reservoir advisory (%1$gU)" = "Reservoir bijna leeg advies (%1$gE)";
 
 /* Description for low reservoir alarm */
 "Low reservoir advisory alarm" = "Reservoir bijna leeg alarm";
 
 /* Title for RileyLink low battery alert */
-"Low RileyLink Battery" = "Batterij RileyLink Bijna Leeg";
+"Low RileyLink Battery" = "Batterij RileyLink bijna leeg";
 
 /* Recovery suggestion when no RileyLink is available */
-"Make sure your RileyLink is nearby and powered on" = "Zorg ervoor dat je RileyLink dichtbij is en aan staat";
+"Make sure your RileyLink is nearby and powered on" = "Zorg ervoor dat je RileyLink dicht bij is en aan staat";
 
 /* Status highlight when manual temp basal is running. */
-"Manual Basal" = "Handmatig Basaal";
+"Manual Basal" = "Handmatig basaal";
 
 /* Pod memory initialized */
 "Memory initialized" = "Geheugen geïnitialiseerd";
@@ -175,10 +175,10 @@
 
 /* Alert content body for multiCommand pod alert
    Alert content title for multiCommand pod alert */
-"Multiple Command Alert" = "Meerdere Commando's Melding";
+"Multiple Command Alert" = "Meerdere commando's melding";
 
 /* Pod alert state when no alerts are active */
-"No alerts" = "Geen Waarschuwingen";
+"No alerts" = "Geen waarschuwingen";
 
 /* Description for BeepPreference.silent */
 "No confidence reminders are used." = "Er worden geen bevestigingsherinneringen gebruikt.";
@@ -188,10 +188,10 @@
 
 /* Status highlight message for emptyReservoir alarm.
    Status highlight that a pump is out of insulin. */
-"No Insulin" = "Geen Insuline";
+"No Insulin" = "Geen insuline";
 
 /* Status highlight that when no pod is paired. */
-"No Pod" = "Geen Pod";
+"No Pod" = "Geen pod";
 
 /* Error message shown when no pod is paired */
 "No pod paired" = "Geen pod gekoppeld";
@@ -210,7 +210,7 @@
 "Normal" = "Normaal";
 
 /* Description for MessageError notEnoughData */
-"Not enough data" = "Onvoldoende Gegevens";
+"Not enough data" = "Onvoldoende gegevens";
 
 /* Description for Occlusion detected pod fault */
 "Occlusion detected" = "Verstopping gedetecteerd";
@@ -282,16 +282,16 @@
 "Pod Occlusion" = "Podverstopping";
 
 /* 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";
@@ -319,13 +319,13 @@
 "Resume delivery" = "Hervat toediening";
 
 /* Alert content title for suspendEnded pod alert */
-"Resume Insulin" = "Hervat Insuline";
+"Resume Insulin" = "Hervat insuline";
 
 /* The format string describing a resume. (1: Time)(2: Scheduled certainty */
 "Resume: %1$@ %2$@" = "Hervat: %1$@ %2$@";
 
 /* Delivery status when basal is running */
-"Scheduled Basal" = "Ingesteld Basaal";
+"Scheduled Basal" = "Ingesteld basaal";
 
 /* Description for shutdown imminent */
 "Shutdown imminent" = "Afsluiting nadert";
@@ -344,7 +344,7 @@
 
 /* Alert content body for suspendInProgress pod alert
    Alert content title for suspendInProgress pod alert */
-"Suspend In Progress Reminder" = "Onderbreking in Uitvoering Herinnering";
+"Suspend In Progress Reminder" = "Herinnering 'Onderbreking in uitvoering'";
 
 /* Description for suspend time expired */
 "Suspend time expired" = "Onderbrekingstijd verstreken";
@@ -368,7 +368,7 @@
 "Tank power activated" = "Opslag power geactiveerd";
 
 /* Pump Event title for UnfinalizedDose with doseType of .tempBasal */
-"Temp Basal" = "Tijdelijk Basaal";
+"Temp Basal" = "Tijdelijk basaal";
 
 /* Error message shown when temp basal could not be set due to existing temp basal in progress */
 "Temp basal in progress" = "Tijdelijk basaal wordt uitgevoerd";
@@ -386,7 +386,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 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.";
 
 /* Alert content title for timeOffsetChangeDetected pod alert */
-"Time Change Detected" = "Tijdsverschil Ontdekt";
+"Time Change Detected" = "Tijdsverschil ontdekt";
 
 /* The format string for pod expiration notification body (1: time until expiration) */
 "Time to replace your pod! Your pod will expire in %1$@" = "Tijd om je pod te vervangen! Vervang je pod in %1$@";

+ 1 - 1
Dependencies/OmniKit/OmniKitUI/Resources/de.lproj/Localizable.strings

@@ -446,7 +446,7 @@
 "Pairing..." = "Koppeln…";
 
 /* No comment provided by engineer. */
-"Percent = %lf" = "Percent = %lf";
+"Percent = %lf" = "Prozent = %lf";
 
 /* The title of the cell showing the pod pi version */
 "PI Version" = "PI-Version";

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

@@ -69,13 +69,13 @@
 "30 minutes" = "30 minuten";
 
 /* The title of the cell showing the pod activated at time */
-"Active Time" = "Werkzame Tijd";
+"Active Time" = "Werkzame tijd";
 
 /* Section header for activity section */
 "Activity" = "Activiteit";
 
 /* Text indicating ongoing pump time synchronization */
-"Adjusting Pump Time..." = "Pomptijd Aanpassen...";
+"Adjusting Pump Time..." = "Pomptijd aanpassen...";
 
 /* The title of the cell showing alarm status */
 "Alarms" = "Alarmen";
@@ -96,7 +96,7 @@
 "Assigned Address" = "Toegewezen adres";
 
 /* Title for Attach Pod screen */
-"Attach Pod" = "Pod Aanbrengen";
+"Attach Pod" = "Pod aanbrengen";
 
 /* Description string above progress indicator while attempting to re-establish communication from an unacknowledged command */
 "Attemping to re-establish communication" = "Poging om de communicatie te herstellen";
@@ -117,7 +117,7 @@
 "Cancel" = "Annuleer";
 
 /* Button title to cancel manual basal */
-"Cancel Manual Basal" = "Annuleer Handmatige Basaal";
+"Cancel Manual Basal" = "Annuleer handmatige basaal";
 
 /* Insert cannula action button accessibility label when cannula insertion succeeded */
 "Cannula inserted successfully. Continue." = "Canule succesvol ingebracht. Ga verder.";
@@ -129,25 +129,25 @@
 "Change Pod now. Insulin delivery will stop in %1$@ or when no more insulin remains." = "Vervang Pod nu. De insulinetoediening stop over %1$@ of wanneer er geen insuline meer over is.";
 
 /* The title of the command to change pump time zone */
-"Change Time Zone" = "Verander Tijdzone";
+"Change Time Zone" = "Verander tijdzone";
 
 /* Progress message for changing pod time. */
-"Changing time…" = "Tijd Aanpassen...";
+"Changing time…" = "Tijd aanpassen...";
 
 /* Title for check cannula screen */
-"Check Cannula" = "Controleer Canule";
+"Check Cannula" = "Controleer canule";
 
 /* Label text for step three of attach pod instructions */
 "Check Pod, apply to site, then confirm pod attachment." = "Controleer de Pod, breng aan op de infusieplaats en bevestig dat de pod goed zit vastgeplakt.";
 
 /* Insert cannula action button accessibility label checking insertion */
-"Checking Insertion" = "Controleer Inbrengen Canule";
+"Checking Insertion" = "Controleer inbrengen canule";
 
 /* Cannula insertion button text while checking insertion */
 "Checking..." = "Controleren...";
 
 /* Title for uncertainty recovered screen */
-"Comms Recovered" = "Comms Hersteld";
+"Comms Recovered" = "Comms hersteld";
 
 /* Text for confidence reminders navigation link */
 "Confidence Reminders" = "Bevestigingsherinneringen";
@@ -162,13 +162,13 @@
 "Confirm" = "Bevestigen";
 
 /* Alert title for confirm pod attachment */
-"Confirm Pod Attachment" = "Bevestig Vastgeplakte Pod";
+"Confirm Pod Attachment" = "Bevestig vastgeplakte pod";
 
 /* The title of the continue action in an action sheet */
-"Continue" = "Ga Verder";
+"Continue" = "Ga verder";
 
 /* Title for critical alerts description */
-"Critical Alerts" = "Kritieke Meldingen";
+"Critical Alerts" = "Kritieke meldingen";
 
 /* Unit for singular day in pod life remaining */
 "day" = "dag";
@@ -209,17 +209,17 @@
 "Devices" = "Apparaten";
 
 /* Title text for button to disable bolus beeps */
-"Disable Bolus Beeps" = "Stop Gebruik Boluspiepjes";
+"Disable Bolus Beeps" = "Stop gebruik boluspiepjes";
 
 /* Pairing interface navigation bar button text for discard pod action
    Text for discard pod button */
-"Discard Pod" = "Gooi Pod Weg";
+"Discard Pod" = "Gooi pod weg";
 
 /* No comment provided by engineer. */
 "Done" = "Gereed";
 
 /* Title text for button to enable bolus beeps */
-"Enable Bolus Beeps" = "Boluspiepjes Inschakelen";
+"Enable Bolus Beeps" = "Boluspiepjes inschakelen";
 
 /* Accessibility label indicating an error occurred */
 "Error" = "Fout";
@@ -231,16 +231,16 @@
 "Error enabling bolus beeps" = "Fout in toepassen gebruik boluspiepjes";
 
 /* The alert title for a resume error */
-"Error Resuming" = "Fout Bij Hervatten";
+"Error Resuming" = "Fout bij hervatten";
 
 /* The alert title for a suspend error */
-"Error Suspending" = "Fout Bij Onderbreken";
+"Error Suspending" = "Fout bij onderbreken";
 
 /* The title of the cell showing the pod expiration reminder date */
 "Expiration Reminder" = "Vervaldatumherinnering";
 
 /* Label text for expiration reminder default row */
-"Expiration Reminder Default" = "Standaard Herinnering";
+"Expiration Reminder Default" = "Standaard herinnering";
 
 /* The title of the cell showing the pod expiration after expiry */
 "Expired" = "Verlopen";
@@ -249,25 +249,25 @@
 "Expires" = "Vervalt";
 
 /* Alert title for failing to cancel manual basal error */
-"Failed to Cancel Manual Basal" = "Handmatig Basaal Annuleren Mislukt";
+"Failed to Cancel Manual Basal" = "Handmatig basaal annuleren mislukt";
 
 /* Alert title for resume error */
-"Failed to Resume Insulin Delivery" = "Insulinetoediening Hervatten Mislukt";
+"Failed to Resume Insulin Delivery" = "Insulinetoediening hervatten mislukt";
 
 /* Alert title for time sync error */
-"Failed to Set Pump Time" = "Pomptijd Instellen Mislukt";
+"Failed to Set Pump Time" = "Pomptijd instellen mislukt";
 
 /* Alert title for suspend error */
-"Failed to Suspend Insulin Delivery" = "Insulinetoediening Onderbreken Mislukt";
+"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.";
 
 /* Alert title for error when updating expiration reminder */
-"Failed to Update Expiration Reminder" = "Vervaldatumherinnering Bijwerken Mislukt";
+"Failed to Update Expiration Reminder" = "Vervaldatumherinnering bijwerken mislukt";
 
 /* Alert title for error when updating low reservoir reminder */
-"Failed to Update Low Reservoir Reminder" = "Bijwerken Reservoir Bijna Leeg Herinnering Mislukt";
+"Failed to Update Low Reservoir Reminder" = "Bijwerken reservoir bijna leeg herinnering mislukt";
 
 /* Pod life HUD view label */
 "Fault" = "Fout";
@@ -282,7 +282,7 @@
 "Finish pod setup" = "Rond podinstallatie af";
 
 /* Action button title to continue at Setup Complete */
-"Finish Setup" = "Voltooi Installatie";
+"Finish Setup" = "Voltooi setup";
 
 /* Accessibility format string for (1: localized volume)(2: time) */
 "Greater than %1$@ units remaining at %2$@" = "Meer dan %1$@ eenheden resterend om %2$@";
@@ -303,7 +303,7 @@
 "Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Een gedeeltelijk ingestelde pod moet eerst worden gedeactiveerd voordat er geprobeerd wordt een nieuwe pod te koppelen. Deactiveer pod en gooi weg.";
 
 /* Button title to insert cannula during setup */
-"Insert Cannula" = "Canule Inbrengen";
+"Insert Cannula" = "Canule inbrengen";
 
 /* Label text indicating insertion finished. */
 "Inserted" = "Ingebracht";
@@ -330,7 +330,7 @@
 "Insulin delivery will be stopped until you resume manually. When would you like Loop to remind you to resume delivery?" = "De insulinetoediening wordt gestopt totdat je de toediening handmatig hervat. Wanneer wil je dat Loop je eraan herinnert de toediening te hervatten?";
 
 /* Header for insulin remaining on pod settings screen */
-"Insulin Remaining" = "Resterende Insuline";
+"Insulin Remaining" = "Resterende insuline";
 
 /* Text for confidence reminders navigation link
    Title for insulin type selection screen */
@@ -364,10 +364,10 @@
 
 /* Label for low reservoir reminder row
    Title for low reservoir reminder edit page */
-"Low Reservoir Reminder" = "Herinnering Reservoir Bijna Leeg";
+"Low Reservoir Reminder" = "Herinnering 'Reservoir bijna leeg'";
 
 /* The action string on pod status page when pod data is stale */
-"Make sure your phone and pod are close to each other. If communication issues persist, move to a new area." = "Zorg ervoor dat je telefoon en pod zich dicht bij elkaar bevinden. Als de communicatieproblemen aanhouden, ga dan naar een andere plek.";
+"Make sure your phone and pod are close to each other. If communication issues persist, move to a new area." = "Zorg ervoor dat je iPhone en Pod zich dicht bij elkaar bevinden. Als de communicatieproblemen aanhouden, ga dan naar een andere plek.";
 
 /* Unit for singular minute in pod life remaining */
 "minute" = "minuut";
@@ -376,7 +376,7 @@
 "minutes" = "minuten";
 
 /* Alert title for missing temp basal configuration */
-"Missing Config" = "Configuratie Niet Compleet";
+"Missing Config" = "Configuratie niet compleet";
 
 /* String shown on pod details for active time when conversion fails.
    String shown on pod details for last status date when not available.
@@ -393,17 +393,17 @@
 "No\nDelivery" = "Geen\nToediening";
 
 /* Error message for reservoir view when reservoir empty */
-"No Insulin" = "Geen Insuline";
+"No Insulin" = "Geen insuline";
 
 /* Label for pod life state when no pod paired
    Text shown in insulin remaining space when no pod is paired */
 "No Pod" = "Geen Pod";
 
 /* Value text for no expiration reminder */
-"No Reminder" = "Geen Herinnering";
+"No Reminder" = "Geen herinnering";
 
 /* Continue pairing button title of in pairing cancel modal */
-"No, Continue With Pod" = "Nee, Ga Verder Met Pod";
+"No, Continue With Pod" = "Nee, ga verder met Pod";
 
 /* Button text to cancel pump time sync */
 "No, Keep Pump As Is" = "Nee, laat de pomp ongewijzigd";
@@ -419,13 +419,13 @@
 "Numbers" = "Nummers";
 
 /* Title for omnipod reminders section */
-"Omnipod Reminders" = "Omnipod Herinneringen";
+"Omnipod Reminders" = "Omnipod herinneringen";
 
 /* Button title to pair with pod during setup */
 "Pair" = "Koppel";
 
 /* The title of the command to pair new pod */
-"Pair New Pod" = "Koppel Nieuwe Pod";
+"Pair New Pod" = "Koppel nieuwe Pod";
 
 /* Navigation bar title for PairPodView
    Pod pairing action button text while ready to pair
@@ -434,7 +434,7 @@
 "Pair Pod" = "Koppel Pod";
 
 /* Pairing action button accessibility label while ready to pair */
-"Pair pod." = "Koppel pod.";
+"Pair pod." = "Koppel Pod.";
 
 /* Label text indicating pairing finished. */
 "Paired" = "Gekoppeld";
@@ -452,7 +452,7 @@
 "PI Version" = "PI versie";
 
 /* The title of the command to play test beeps */
-"Play Test Beeps" = "Testpiepjes Afspelen";
+"Play Test Beeps" = "Testpiepjes afspelen";
 
 /* Progress message for play test beeps. */
 "Play Test Beeps…" = "Speelt testpiepjes…";
@@ -471,7 +471,7 @@
 
 /* description label for activated at time pod details row
    Label for pod insertion row */
-"Pod Activated" = "Pod Geactiveerd";
+"Pod Activated" = "Pod geactiveerd";
 
 /* Label describing pod age view */
 "Pod Age" = "Podleeftijd";
@@ -487,16 +487,16 @@
 
 /* Error message for reservoir view when pod expired
    Label for pod expiration row, past tense */
-"Pod Expired" = "Pod Verlopen";
+"Pod Expired" = "Pod verlopen";
 
 /* Label for pod expiration row */
-"Pod Expires" = "Pod Verloopt";
+"Pod Expires" = "Pod verloopt";
 
 /* Label for pod life state when time remaining */
 "Pod expires in" = "Pod verloopt over";
 
 /* description label for pod fault details */
-"Pod Fault Details" = "Pod Foutgegevens";
+"Pod Fault Details" = "Pod foutgegevens";
 
 /* Error message for reservoir view when pod occlusion checks failed */
 "Pod Occlusion" = "Podverstopping";
@@ -505,7 +505,7 @@
 "Pod paired successfully. Continue." = "Pod succesvol gekoppeld. Ga verder.";
 
 /* Title of the pod settings view controller */
-"Pod Settings" = "Pod Instellingen";
+"Pod Settings" = "Pod instellingen";
 
 /* Title for PodSetupView */
 "Pod Setup" = "Podinstallatie";
@@ -517,7 +517,7 @@
 "Previous Pod" = "Vorige Pod";
 
 /* Text for previous pod information row */
-"Previous Pod Information" = "Informatie Vorige Pod";
+"Previous Pod Information" = "Informatie vorige Pod";
 
 /* The text of the loading label when pod is primed */
 "Primed" = "Voorbereid";
@@ -541,7 +541,7 @@
 "Remaining" = "Resterend";
 
 /* Title for remove pod modal */
-"Remove Pod from Body" = "Verwijder Pod van Lichaam";
+"Remove Pod from Body" = "Verwijder Pod van lichaam";
 
 /* Title for Omnipod PumpManager deletion action sheet. */
 "Remove Pump" = "Verwijder Pomp";
@@ -554,13 +554,13 @@
 "Replace Pod" = "Vervang Pod";
 
 /* The title of the command to replace pod when there is a pod fault */
-"Replace Pod Now" = "Vervang Pod Nu";
+"Replace Pod Now" = "Vervang Pod nu";
 
 /* The title of the cell showing reservoir status */
 "Reservoir" = "Reservoir";
 
 /* Text for suspend resume button when insulin delivery is suspended */
-"Resume Insulin Delivery" = "Hervat Insulinetoediening";
+"Resume Insulin Delivery" = "Hervat insulinetoediening";
 
 /* Text for suspend resume button when insulin delivery is resuming */
 "Resuming insulin delivery..." = "Insulinetoediening hervatten...";
@@ -568,16 +568,16 @@
 /* Action button description for deactivate after failed attempt
    Cannula insertion button text while showing error
    Pod pairing action button text while showing error */
-"Retry" = "Opnieuw Proberen";
+"Retry" = "Opnieuw proberen";
 
 /* Button title for retrying pod deactivation */
-"Retry Pod Deactivation" = "Probeer Deactivering Pod Opnieuw";
+"Retry Pod Deactivation" = "Probeer deactivering Pod opnieuw";
 
 /* bodyText for RileyLinkSetupView */
 "RileyLink allows for communication with the pump over Bluetooth" = "RileyLink staat communicatie met de pomp toe via Bluetooth";
 
 /* Navigation title for RileyLinkSetupView */
-"RileyLink Setup" = "RileyLink Installatie";
+"RileyLink Setup" = "RileyLink setup";
 
 /* Title of button to save delivery limit settings
    Title of button to sync basal profile when no pod paired */
@@ -588,16 +588,16 @@
 "Saving..." = "Opslaan...";
 
 /* The detail text of the basal row when pod is running scheduled basal */
-"Schedule" = "Plan";
+"Schedule" = "Schema";
 
 /* Title of insulin delivery section */
-"Scheduled Basal" = "Ingesteld Basaal";
+"Scheduled Basal" = "Ingesteld basaal";
 
 /* Card title for scheduled reminder
    Scheduled reminder card title on NotificationSettingsView
    Title for scheduled expiration reminder edit page
    Title of SetupCompleteView */
-"Scheduled Reminder" = "Geplande Herinnering";
+"Scheduled Reminder" = "Geplande herinnering";
 
 /* Title text for insulin type confirmation page */
 "Select the type of insulin that you will be using in this pod." = "Selecteer het type insuline dat je in deze pod gaat gebruiken.";
@@ -606,19 +606,19 @@
 "Sequence Number" = "Volgnummer";
 
 /* Button text for setting manual temporary basal rate */
-"Set Temporary Basal" = "Tijdelijk Basaal Instellen";
+"Set Temporary Basal" = "Tijdelijk basaal instellen";
 
 /* Button title to set temporary basal rate */
 "Set Temporary Basal Rate" = "Tijdelijke Basaalsnelheid Instellen";
 
 /* Title for setup complete screen */
-"Setup Complete" = "Configuratie Voltooid";
+"Setup Complete" = "Setup voltooid";
 
 /* Error message for reservoir view during general pod fault */
 "Signal Loss" = "Signaalverlies";
 
 /* No comment provided by engineer. */
-"Skip Omnipod Onboarding?" = "Omnipod Onboarding Overslaan?";
+"Skip Omnipod Onboarding?" = "Omnipod Onboarding overslaan?";
 
 /* The title of the status section in settings */
 "Status" = "Status";
@@ -627,10 +627,10 @@
 "Succeeded" = "Geslaagd";
 
 /* Title for suspend duration selection action sheet */
-"Suspend Delivery" = "Onderbreek Toediening";
+"Suspend Delivery" = "Onderbreek toediening";
 
 /* Text for suspend resume button when insulin delivery active */
-"Suspend Insulin Delivery" = "Onderbreek Insulinetoediening";
+"Suspend Insulin Delivery" = "Onderbreek insulinetoediening";
 
 /* The detail text of the basal row when pod is suspended */
 "Suspended" = "Onderbroken";
@@ -639,10 +639,10 @@
 "Suspended At" = "Onderbroken om";
 
 /* Text for suspend resume button when insulin delivery is suspending */
-"Suspending insulin delivery..." = "Insulinetoediening Onderbreken...";
+"Suspending insulin delivery..." = "Insulinetoediening onderbreken...";
 
 /* Title text for the button to delete Omnipod PumpManager */
-"Switch from Omnipod Pumps" = "Verwissel Omnipod Pompen";
+"Switch from Omnipod Pumps" = "Verwissel Omnipod pompen";
 
 /* Label for PumpManager deletion button */
 "Switch to other insulin delivery device" = "Schakel over op een ander insulinetoedieningsapparaat";
@@ -657,16 +657,16 @@
 "Tap below to start cannula insertion." = "Tik hieronder om het inbrengen van de canule te starten.";
 
 /* Navigation Title for ManualTempBasalEntryView */
-"Temporary Basal" = "Tijdelijke Basaal";
+"Temporary Basal" = "Tijdelijke basaal";
 
 /* Alert title for a failure to set temporary basal */
-"Temporary Basal Failed" = "Tijdelijk Basaal Mislukt";
+"Temporary Basal Failed" = "Tijdelijk basaal mislukt";
 
 /* The title of the command to run the test command */
 "Test Command" = "Testcommando";
 
 /* Progress message for testing commands. */
-"Testing Commands…" = "Test Commando’s…";
+"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.";
@@ -708,7 +708,7 @@
 
 /* Title for pod sync time action sheet.
    title for time change detected notice */
-"Time Change Detected" = "Tijdsverschil Ontdekt";
+"Time Change Detected" = "Tijdsverschil ontdekt";
 
 /* No comment provided by engineer. */
 "Toggle sign" = "Schakel symbool";
@@ -717,7 +717,7 @@
 "Too many entries" = "Te veel invoer";
 
 /* description label for total delivery pod details row */
-"Total Delivery" = "Totaal Toegediend";
+"Total Delivery" = "Totaal toegediend";
 
 /* Units for showing temp basal rate */
 "U/hr" = "E/uur";
@@ -726,7 +726,7 @@
 "Unable to deactivate pod. Please continue and pair a new one." = "Onmogelijk om pod te deactiveren. Ga verder en koppel een nieuwe pod.";
 
 /* Title of delivery uncertainty recovery page */
-"Unable to Reach Pod" = "Kan Pod Niet Bereiken";
+"Unable to Reach Pod" = "Kan Pod niet bereiken";
 
 
 /* Alert format string for a failure to set temporary basal. (1: error description) */
@@ -736,7 +736,7 @@
 "Unable to set a temporary basal rate: %1$@\n\n%2$@" = "Kan geen tijdelijke basaalsnelheid instellen: %1$@ \n\n%2$@";
 
 /* Label for pod life state when pod not fully activated */
-"Unfinished Activation" = "Onvoltooide Activering";
+"Unfinished Activation" = "Onvoltooide activering";
 
 /* Label for pod life state when pod not fully deactivated */
 "Unfinished deactivation" = "Onvoltooide deactivering";
@@ -751,10 +751,10 @@
 "Yes" = "Ja";
 
 /* Button title for confirm deactivation option */
-"Yes, Deactivate Pod" = "Ja, Deactiveer Pod";
+"Yes, Deactivate Pod" = "Ja, deactiveer Pod";
 
 /* Button text to confirm pump time sync */
-"Yes, Sync to Current Time" = "Ja, Synchroniseer met Huidige Tijd";
+"Yes, Sync to Current Time" = "Ja, synchroniseer met huidige tijd";
 
 /* bodyText for PodSetupView */
 "You will now begin the process of configuring your reminders, filling your Pod with insulin, pairing to your device and placing it on your body." = "Je begint nu met het configureren van je herinneringen, het vullen van je Pod met insuline, het koppelen van je apparaat en het op je lichaam plaatsen.";

+ 13 - 13
Dependencies/rileylink_ios/RileyLinkKitUI/nl.lproj/Localizable.strings

@@ -26,13 +26,13 @@
 "Connection LED" = "Verbindings-LED";
 
 /* The title of the section for connection monitoring */
-"Connection Monitoring" = "Verbindingsbewaking";
+"Connection Monitoring" = "Controle van de verbinding";
 
 /* The title of the cell showing BLE connection state */
 "Connection State" = "Verbindingsstatus";
 
 /* The title of the cell for connection vibration */
-"Connection Vibration" = "Verbinding Trilling";
+"Connection Vibration" = "Verbinding vibraties";
 
 /* The title of the section describing the device */
 "Device" = "Apparaat";
@@ -47,7 +47,7 @@
 "Disconnecting" = "Ontkoppelen";
 
 /* The title of the cell for sounding device finding piezo */
-"Find Device" = "Zoek Apparaat";
+"Find Device" = "Zoek apparaat";
 
 /* The title of the cell showing firmware version */
 "Firmware" = "Firmware";
@@ -56,19 +56,19 @@
 "Frequency" = "Frequentie";
 
 /* The title of the command to fetch RileyLink statistics */
-"Get RileyLink Statistics" = "RileyLink Statistieken Ophalen";
+"Get RileyLink Statistics" = "RileyLink statistieken ophalen";
 
 /* Progress message for getting statistics. */
-"Get Statistics…" = "Statistieken Ophalen...";
+"Get Statistics…" = "Statistieken ophalen...";
 
 /* The title of the cell showing Lighten Red LED */
-"Lighten Red LED" = "Rode LED Verlichten";
+"Lighten Red LED" = "Rode LED verlichten";
 
 /* The title of the cell showing Lighten Yellow LED */
-"Lighten Yellow LED" = "Gele LED Verlichten";
+"Lighten Yellow LED" = "Gele LED verlichten";
 
 /* The title of the cell showing battery level */
-"Low Battery Alert" = "Batterij Bijna Leeg Alarm";
+"Low Battery Alert" = "Alarm: batterij bijna leeg";
 
 /* The title of the cell showing device name */
 "Name" = "Naam";
@@ -88,19 +88,19 @@
 
 /* The title of the section for orangelink commands
    The title of the section for rileylink commands */
-"Test Commands" = "Commando's Testen";
+"Test Commands" = "Commando's testen";
 
 /* The title of the cell showing Test Vibration */
-"Test Vibration" = "Test Trillingen";
+"Test Vibration" = "Test vibraties";
 
 /* The title of the command to update diagnostic LEDs */
-"Toggle Diagnostic LEDs" = "Tokkel de Diagnostische LED's";
+"Toggle Diagnostic LEDs" = "Schakelen tussen diagnostische LED's";
 
 /* Progress message for changing diagnostic LED mode */
 "Updating diagnostic LEDs mode" = "Diagnostische LED-modus aan het bijstellen";
 
 /* The title of the cell showing uptime */
-"Uptime" = "Uptime";
+"Uptime" = "Tijd";
 
 /* The title of the cell showing ORL */
-"Voltage" = "Spanning";
+"Voltage" = "Voltage";

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

@@ -62,10 +62,10 @@
 "Get Statistics…" = "Получить статистику…";
 
 /* The title of the cell showing Lighten Red LED */
-"Lighten Red LED" = "Lighten Red LED";
+"Lighten Red LED" = "Светло-красный светодиод";
 
 /* The title of the cell showing Lighten Yellow LED */
-"Lighten Yellow LED" = "Lighten Yellow LED";
+"Lighten Yellow LED" = "Светло-желтый светодиод";
 
 /* The title of the cell showing battery level */
 "Low Battery Alert" = "Низкий заряд";

+ 12 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -26,6 +26,9 @@
 		19795118275953E50044850D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; };
 		198377D2266BFFF6004DE65E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; };
 		199561C1275E61A50077B976 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 199561C0275E61A50077B976 /* HealthKit.framework */; };
+		19A910302A24BF6300C8951B /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A9102F2A24BF6300C8951B /* StatsView.swift */; };
+		19A910362A24D6D700C8951B /* DateFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A910352A24D6D700C8951B /* DateFilter.swift */; };
+		19A910382A24EF3200C8951B /* ChartsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A910372A24EF3200C8951B /* ChartsView.swift */; };
 		19B0EF2128F6D66200069496 /* Statistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B0EF2028F6D66200069496 /* Statistics.swift */; };
 		19D466A329AA2B80004D5F33 /* FPUConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A229AA2B80004D5F33 /* FPUConfigDataFlow.swift */; };
 		19D466A529AA2BD4004D5F33 /* FPUConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */; };
@@ -532,6 +535,9 @@
 		199561C0275E61A50077B976 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS8.0.sdk/System/Library/Frameworks/HealthKit.framework; sourceTree = DEVELOPER_DIR; };
 		199732B4271B72DD00129A3F /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
 		199732B5271B9EE900129A3F /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
+		19A9102F2A24BF6300C8951B /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
+		19A910352A24D6D700C8951B /* DateFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFilter.swift; sourceTree = "<group>"; };
+		19A910372A24EF3200C8951B /* ChartsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartsView.swift; sourceTree = "<group>"; };
 		19B0EF2028F6D66200069496 /* Statistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistics.swift; sourceTree = "<group>"; };
 		19C166682756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		19C166692756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -1108,6 +1114,8 @@
 			isa = PBXGroup;
 			children = (
 				19F95FF929F1102A00314DDC /* StatRootView.swift */,
+				19A9102F2A24BF6300C8951B /* StatsView.swift */,
+				19A910372A24EF3200C8951B /* ChartsView.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -1591,6 +1599,7 @@
 				FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */,
 				1967DFBD29D052C200759F30 /* Icons.swift */,
 				19D4E4EA29FC6A9F00351451 /* TIRforChart.swift */,
+				19A910352A24D6D700C8951B /* DateFilter.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -2552,6 +2561,7 @@
 				3811DF0225CA9FEA00A708ED /* Credentials.swift in Sources */,
 				19DC678529CA67A400FD9EC4 /* OverrideProfilesRootView.swift in Sources */,
 				389A572026079BAA00BC102F /* Interpolation.swift in Sources */,
+				19A910382A24EF3200C8951B /* ChartsView.swift in Sources */,
 				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
 				19D466A729AA2C22004D5F33 /* FPUConfigStateModel.swift in Sources */,
 				38E44528274E401C00EC9A94 /* Protected.swift in Sources */,
@@ -2593,6 +2603,7 @@
 				38EA05DA261F6E7C0064E39B /* SimpleLogReporter.swift in Sources */,
 				3811DE6125C9D4D500A708ED /* ViewModifiers.swift in Sources */,
 				3811DEAC25C9D88300A708ED /* NightscoutManager.swift in Sources */,
+				19A910302A24BF6300C8951B /* StatsView.swift in Sources */,
 				CEB434E528B8FF5D00B70274 /* UIColor.swift in Sources */,
 				190EBCCB29FF13CB00BA767D /* StatConfigRootView.swift in Sources */,
 				3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */,
@@ -2656,6 +2667,7 @@
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,
 				2BE9A6FA20875F6F4F9CD461 /* PumpSettingsEditorProvider.swift in Sources */,
 				6B9625766B697D1C98E455A2 /* PumpSettingsEditorStateModel.swift in Sources */,
+				19A910362A24D6D700C8951B /* DateFilter.swift in Sources */,
 				A0B8EC8CC5CD1DD237D1BCD2 /* PumpSettingsEditorRootView.swift in Sources */,
 				E06B911A275B5EEA003C04B6 /* Array+Extension.swift in Sources */,
 				38EA0600262091870064E39B /* BolusProgressViewStyle.swift in Sources */,

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2 - 1
FreeAPS/Resources/javascript/bundle/determine-basal.js


+ 50 - 91
FreeAPS/Sources/APS/APSManager.swift

@@ -205,9 +205,26 @@ final class BaseAPSManager: APSManager, Injectable {
         debug(.apsManager, "Starting loop with a delay of \(UIApplication.shared.backgroundTimeRemaining.rounded())")
 
         lastStartLoopDate = Date()
+
+        var previousLoop = [LoopStatRecord]()
+        var interval: Double?
+
+        coredataContext.performAndWait {
+            let requestStats = LoopStatRecord.fetchRequest() as NSFetchRequest<LoopStatRecord>
+            let sortStats = NSSortDescriptor(key: "end", ascending: false)
+            requestStats.sortDescriptors = [sortStats]
+            requestStats.fetchLimit = 1
+            try? previousLoop = coredataContext.fetch(requestStats)
+
+            if (previousLoop.first?.end ?? .distantFuture) < lastStartLoopDate {
+                interval = roundDouble((lastStartLoopDate - (previousLoop.first?.end ?? Date())).timeInterval / 60, 1)
+            }
+        }
+
         var loopStatRecord = LoopStats(
             start: lastStartLoopDate,
-            loopStatus: "Starting"
+            loopStatus: "Starting",
+            interval: interval
         )
 
         isLooping.send(true)
@@ -810,96 +827,36 @@ final class BaseAPSManager: APSManager, Injectable {
                 }
 
                 var lsr = [LoopStatRecord]()
-                var successRate: Double?
-                var successNR = 0
-                var errorNR = 0
-                var minimumInt = 999.0
-                var maximumInt = 0.0
-                var minimumLoopTime = 9999.0
-                var maximumLoopTime = 0.0
-                var timeIntervalLoops = 0.0
-                var previousTimeLoop = Date()
-                var timeForOneLoop = 0.0
-                var averageLoopTime = 0.0
-                var timeForOneLoopArray: [Double] = []
-                var medianLoopTime = 0.0
-                var timeIntervalLoopArray: [Double] = []
-                var medianInterval = 0.0
-                var averageIntervalLoops = 0.0
-                var averageLoopDuration = 0.0
 
                 let requestLSR = LoopStatRecord.fetchRequest() as NSFetchRequest<LoopStatRecord>
                 requestLSR.predicate = NSPredicate(
-                    format: "start > %@",
+                    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)
-
-                if lsr.isNotEmpty {
-                    var i = 0.0
-                    if let loopEnd = lsr[0].end {
-                        previousTimeLoop = loopEnd
-                    }
-                    for each in lsr {
-                        if let loopEnd = each.end {
-                            let loopDuration = each.duration
-
-                            if each.loopStatus!.contains("Success") {
-                                successNR += 1
-                            } else {
-                                errorNR += 1
-                            }
-
-                            i += 1
-                            timeIntervalLoops = (previousTimeLoop - (each.start ?? previousTimeLoop)).timeInterval / 60
-
-                            if timeIntervalLoops > 0.0, i != 1 {
-                                timeIntervalLoopArray.append(timeIntervalLoops)
-                            }
-                            if timeIntervalLoops > maximumInt {
-                                maximumInt = timeIntervalLoops
-                            }
-                            if timeIntervalLoops < minimumInt, i != 1 {
-                                minimumInt = timeIntervalLoops
-                            }
-                            timeForOneLoop = loopDuration
-                            timeForOneLoopArray.append(timeForOneLoop)
-
-                            if timeForOneLoop >= maximumLoopTime, timeForOneLoop != 0.0 {
-                                maximumLoopTime = timeForOneLoop
-                            }
-                            if timeForOneLoop <= minimumLoopTime, timeForOneLoop != 0.0 {
-                                minimumLoopTime = timeForOneLoop
-                            }
-                            previousTimeLoop = loopEnd
-                        }
-                    }
-                    successRate = (Double(successNR) / Double(i)) * 100
-
-                    // Average Loop Interval in minutes
-                    let timeOfFirstIndex = lsr[0].start ?? Date()
-                    let lastIndexWithTimestamp = lsr.count - 1
-                    let timeOfLastIndex = lsr[lastIndexWithTimestamp].end ?? Date()
-                    averageLoopTime = (timeOfFirstIndex - timeOfLastIndex).timeInterval / 60 / Double(errorNR + successNR)
-
-                    // Median values
-                    medianLoopTime = medianCalculation(array: timeForOneLoopArray)
-                    medianInterval = medianCalculation(array: timeIntervalLoopArray)
-                    // Average time interval between loops
-                    averageIntervalLoops = timeIntervalLoopArray.reduce(0, +) / Double(timeIntervalLoopArray.count)
-                    // Average loop duration
-                    averageLoopDuration = timeForOneLoopArray.reduce(0, +) / Double(timeForOneLoopArray.count)
-                }
-
-                if minimumInt == 999.0 {
-                    minimumInt = 0.0
-                }
-                if minimumLoopTime == 9999.0 {
-                    minimumLoopTime = 0.0
-                }
+                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]()
 
@@ -1119,18 +1076,18 @@ final class BaseAPSManager: APSManager, Injectable {
                 let nrOfCGMReadings = nr1
 
                 let loopstat = LoopCycles(
-                    loops: successNR + errorNR,
+                    loops: loopNr,
                     errors: errorNR,
                     readings: Int(nrOfCGMReadings),
                     success_rate: Decimal(round(successRate ?? 0)),
-                    avg_interval: roundDecimal(Decimal(averageLoopTime), 1),
-                    median_interval: roundDecimal(Decimal(medianInterval), 1),
-                    min_interval: roundDecimal(Decimal(minimumInt), 1),
-                    max_interval: roundDecimal(Decimal(maximumInt), 1),
-                    avg_duration: Decimal(roundDouble(averageLoopDuration, 2)),
-                    median_duration: Decimal(roundDouble(medianLoopTime, 2)),
-                    min_duration: roundDecimal(Decimal(minimumLoopTime), 2),
-                    max_duration: Decimal(roundDouble(maximumLoopTime, 1))
+                    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))
                 )
 
                 // TIR calcs for every case
@@ -1340,9 +1297,11 @@ final class BaseAPSManager: APSManager, Injectable {
             nLS.end = loopStatRecord.end ?? Date()
             nLS.loopStatus = loopStatRecord.loopStatus
             nLS.duration = loopStatRecord.duration ?? 0.0
+            nLS.interval = loopStatRecord.interval ?? 0.0
 
             try? self.coredataContext.save()
         }
+        print("LoopStatRecords: \(loopStatRecord)")
         print("Test time of LoopStats computation: \(-1 * LoopStatsStartedAt.timeIntervalSinceNow) s")
     }
 

+ 67 - 1
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

+ 63 - 0
FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,9 +1182,69 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
+/* */
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
 /* */
 "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
 
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
+
 /* Service Section */
 "App Icons" = "App Icons";
 

+ 67 - 1
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 109 - 43
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings


+ 64 - 5
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings

@@ -1165,10 +1165,6 @@ Enact a temp Basal or a temp target */
 /* */
 "Normal " = "Normal ";
 
-/* */
-"Add / Delete" = "Add / Delete";
-
-/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1186,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
+/* */
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

+ 67 - 1
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

+ 67 - 1
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

+ 67 - 1
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

+ 67 - 1
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 326 - 259
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings


+ 67 - 1
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Overstyre Profil";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Ingen aktiv overstyring";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Deaktiver SMB";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal profil";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Egendefinert profil";
+
+/* */
+"Profiles" = "Profiler";
+
+/* */
+"More options" = "Flere valg";
+
+/* */
+"Schedule when SMBs are Off" = "Tidsplan for når SMB er av";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Profilens basaldose vil bli justert med prosentsatsen og profilens ISF og CR vil bli omvendt justert med prosentsatsen. \n\nHvis du slår av overstyringen vil alle profilinnstillinger gå tilbake til normalen.";
+"Change ISF and CR" = "Endre ISF og CR";
+
+/* */
+"Change ISF" = "Endre ISF";
+
+/* */
+"Change CR" = "Endre CR";
+
+/* */
+"SMB Minutes" = "SMB minutter";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB minutter";
+
+/* */
+"Start new Profile" = "Start ny profil";
+
+/* */
+"Save as Profile" = "Lagre som profil";
+
+/* */
+"Return to Normal" = "Gå tilbake til normal";
+
+/* Alert */
+"Return to Normal?" = "Gå tilbake til normal?";
+
+/* */
+"This will change settings back to your normal profile." = "Dette vil endre innstillinger tilbake til din vanlige profil.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start profil";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Profilens basaldose vil bli justert med prosentsatsen og profilens ISF og CR vil bli omvendt justert med prosentsatsen.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Ved å starte denne overstyringen, endres profilen og/eller blodsukkermålet som brukes ved looping, gjennom hele den valgte varigheten. Ved å trykke \"Start profil\" vil den nye profilen starte eller du vil kunne redigere den aktive profilen.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Overstyre profilens blodsukkermål";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMB er deaktivert enten ved tidsplan eller gjennom hele varigheten.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " uendelig varighet.";
 
 /* Service Section */
 "App Icons" = "App-ikon";
 
+/* */
+"iAPS Icon" = "iAPS ikon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistikk og startskjerm";
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 137 - 71
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings


+ 67 - 1
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings

@@ -1164,6 +1164,9 @@ Połączono z Nightscout!";
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1181,12 +1184,75 @@ Połączono z Nightscout!";
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

+ 67 - 1
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

+ 67 - 1
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 67 - 1
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings


+ 67 - 1
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

+ 69 - 3
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Aktivera tillfälligt undantag";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Inget undantag aktivt";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Stäng av autobolusar (SMBs)";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profil";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Anpassad Profil";
+
+/* */
+"Profiles" = "Profiler";
+
+/* */
+"More options" = "Fler alternativ";
+
+/* */
+"Schedule when SMBs are Off" = "Schemalägg när autobolusar ska vara av ";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Din vanliga basal kommer att justeras procentuellt enligt ovan, medan din normala korrektionsfaktor och insulinkvot CR kommer att justeras omvänt.\n\nOm du stänger av undantaget kommer varje inställning att återgå till det normala.";
+"Change ISF and CR" = "Ändra ISF and CR";
+
+/* */
+"Change ISF" = "Ändra ISF";
+
+/* */
+"Change CR" = "Ändra CR";
+
+/* */
+"SMB Minutes" = "SMB-minuter";
+
+/* */
+"UAM SMB Minutes" = "UAM-SMB-minuter";
+
+/* */
+"Start new Profile" = "Starta ny profil";
+
+/* */
+"Save as Profile" = "Spara som profil";
+
+/* */
+"Return to Normal" = "Tillbaka till normal profil";
+
+/* Alert */
+"Return to Normal?" = "Tillbaka till normal profil?";
+
+/* */
+"This will change settings back to your normal profile." = "Detta kommer att ändra tillbaka till dina normala inställningar ";
+
+/* Start Profile Alert */
+"Start Profile" = "Starta profil";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Din vanliga basal kommer att justeras procentuellt enligt ovan, medan din normala korrektionsfaktor och insulinkvot CR kommer att bli omvänt justerade.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Om du klickar 'Starta profil' kommer detta att tillfälligt att ändra dina normala inställningar för under hela perioden som du valt";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Ändra målvärde";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " Autobolusar är helt eller delvis av.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " aktiv tillsvidare.";
 
 /* Service Section */
 "App Icons" = "Ikoner";
 
+/* */
+"iAPS Icon" = "iAPS - ikon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistik och Diagram";
 
@@ -1192,10 +1258,10 @@ Enact a temp Basal or a temp target */
 "Delete carb equivalents?" = "Radera dessa poster?";
 
 /* */
-"Meal Presets" = "Måltider";
+"Meal Presets" = "Förval";
 
 /* */
-"Empty" = "Tom";
+"Empty" = "Inget";
 
 /* */
 "Delete Selected Preset" = "Radera detta förval";

+ 67 - 1
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 67 - 1
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings


+ 67 - 1
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings

@@ -1162,6 +1162,9 @@ Enact a temp Basal or a temp target */
 "Override Profiles" = "Override Profiles";
 
 /* */
+"Normal " = "Normal ";
+
+/* */
 "Currently no Override active" = "Currently no Override active";
 
 /* */
@@ -1179,12 +1182,75 @@ Enact a temp Basal or a temp target */
 /* */
 "Disable SMBs" = "Disable SMBs";
 
+/* Your normal Profile. Use a short string */
+"Normal Profile" = "Normal Profile";
+
+/* Custom but unsaved Profile */
+"Custom Profile" = "Custom Profile";
+
+/* */
+"Profiles" = "Profiles";
+
+/* */
+"More options" = "More options";
+
+/* */
+"Schedule when SMBs are Off" = "Schedule when SMBs are Off";
+
 /* */
-"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.\n\nIf you toggle off the override every profile setting will return to normal.";
+"Change ISF and CR" = "Change ISF and CR";
+
+/* */
+"Change ISF" = "Change ISF";
+
+/* */
+"Change CR" = "Change CR";
+
+/* */
+"SMB Minutes" = "SMB Minutes";
+
+/* */
+"UAM SMB Minutes" = "UAM SMB Minutes";
+
+/* */
+"Start new Profile" = "Start new Profile";
+
+/* */
+"Save as Profile" = "Save as Profile";
+
+/* */
+"Return to Normal" = "Return to Normal";
+
+/* Alert */
+"Return to Normal?" = "Return to Normal?";
+
+/* */
+"This will change settings back to your normal profile." = "This will change settings back to your normal profile.";
+
+/* Start Profile Alert */
+"Start Profile" = "Start Profile";
+
+/* */
+"Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage." = "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage.";
+
+/* */
+"Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile." = "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.";
+
+/* Change Target glucose in profile settings */
+"Override Profile Target" = "Override Profile Target";
+
+/* Alert string. Keep spaces. */
+" SMBs are disabled either by schedule or during the entire duration." = " SMBs are disabled either by schedule or during the entire duration.";
+
+/* Alert strings. Keep spaces */
+" infinite duration." = " infinite duration.";
 
 /* Service Section */
 "App Icons" = "App Icons";
 
+/* */
+"iAPS Icon" = "iAPS Icon";
+
 /* Service Section */
 "Statistics and Home View" = "Statistics and Home View";
 

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

@@ -0,0 +1,10 @@
+
+import Foundation
+
+struct DateFilter {
+    var today = Calendar.current.startOfDay(for: Date()) as NSDate
+    var day = Date().addingTimeInterval(-24.hours.timeInterval) as NSDate
+    var week = Date().addingTimeInterval(-7.days.timeInterval) as NSDate
+    var month = Date().addingTimeInterval(-30.days.timeInterval) as NSDate
+    var total = Date().addingTimeInterval(-90.days.timeInterval) as NSDate
+}

+ 5 - 1
FreeAPS/Sources/Models/LoopStats.swift

@@ -5,13 +5,16 @@ struct LoopStats: JSON, Equatable {
     var end: Date?
     var duration: Double?
     var loopStatus: String
+    var interval: Double?
 
     init(
         start: Date,
-        loopStatus: String
+        loopStatus: String,
+        interval: Double?
     ) {
         self.start = start
         self.loopStatus = loopStatus
+        self.interval = interval
     }
 }
 
@@ -21,5 +24,6 @@ extension LoopStats {
         case end
         case duration
         case loopStatus
+        case interval
     }
 }

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

@@ -667,8 +667,9 @@ extension MainChartView {
                 path.addLine(to: CGPoint(x: lastPoint.x, y: Config.basalHeight))
                 path.addLine(to: CGPoint(x: 0, y: Config.basalHeight))
             }
-
-            let endDateTime = dayAgoTime + min(max(screenHours, 2), 24).hours.timeInterval + min(max(screenHours, 2), 24).hours
+            let adjustForOptionalExtraHours = screenHours > 12 ? screenHours - 12 : 0
+            let endDateTime = dayAgoTime + min(max(screenHours - adjustForOptionalExtraHours, 12), 24).hours
+                .timeInterval + min(max(screenHours - adjustForOptionalExtraHours, 12), 24).hours
                 .timeInterval
             let autotunedBasalPoints = findRegularBasalPoints(
                 timeBegin: dayAgoTime,

+ 15 - 4
FreeAPS/Sources/Modules/OverrideProfilesConfig/View/OverrideProfilesRootView.swift

@@ -194,17 +194,28 @@ extension OverrideProfilesConfig {
                                                 .formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))) +
                                                 " min."
                                         ) :
-                                        " infinite duration."
+                                        NSLocalizedString(" infinite duration.", comment: "")
                                 ) +
                                 (
                                     (state.target == 0 || !state.override_target) ? "" :
                                         (" Target: " + state.target.formatted() + " " + state.units.rawValue + ".")
                                 )
-                                + (state.smbIsOff ? " SMBs are disabled." : "")
+                                +
+                                (
+                                    state
+                                        .smbIsOff ?
+                                        NSLocalizedString(
+                                            " SMBs are disabled either by schedule or during the entire duration.",
+                                            comment: ""
+                                        ) : ""
+                                )
                                 +
                                 "\n\n"
                                 +
-                                "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start” will start your new overide or edit your current active override."
+                                NSLocalizedString(
+                                    "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.",
+                                    comment: ""
+                                )
                         }
                         .disabled(
                             (state.percentage == 100 && !state.override_target && !state.smbIsOff) ||
@@ -218,7 +229,7 @@ extension OverrideProfilesConfig {
                             isPresented: $showAlert,
                             actions: {
                                 Button("Cancel", role: .cancel) { state.isEnabled = false }
-                                Button("Start Override", role: .destructive) {
+                                Button("Start Profile", role: .destructive) {
                                     if state._indefinite { state.duration = 0 }
                                     state.isEnabled.toggle()
                                     state.saveSettings()

+ 5 - 6
FreeAPS/Sources/Modules/Stat/StatStateModel.swift

@@ -5,12 +5,11 @@ import Swinject
 extension Stat {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var settings: SettingsManager!
-        @Published var highLimit: Decimal?
-        @Published var lowLimit: Decimal?
-        @Published var overrideUnit: Bool?
-        @Published var layingChart: Bool?
-
-        private(set) var units: GlucoseUnits = .mmolL
+        @Published var highLimit: Decimal = 10 / 0.0555
+        @Published var lowLimit: Decimal = 4 / 0.0555
+        @Published var overrideUnit: Bool = false
+        @Published var layingChart: Bool = false
+        @Published var units: GlucoseUnits = .mmolL
 
         override func subscribe() {
             highLimit = settingsManager.settings.high

+ 324 - 0
FreeAPS/Sources/Modules/Stat/View/ChartsView.swift

@@ -0,0 +1,324 @@
+//
+//  FilteredLoopsView.swift
+//  FreeAPS
+//
+//  Created by Jon Mårtensson on 2023-05-29.
+//
+import Charts
+import CoreData
+import SwiftDate
+import SwiftUI
+
+struct ChartsView: View {
+    @FetchRequest var fetchRequest: FetchedResults<Readings>
+
+    @Binding var highLimit: Decimal
+    @Binding var lowLimit: Decimal
+    @Binding var units: GlucoseUnits
+    @Binding var overrideUnit: Bool
+    @Binding var standing: Bool
+
+    @State var headline: Color = .secondary
+
+    private let conversionFactor = 0.0555
+
+    var body: some View {
+        glucoseChart
+        Rectangle().fill(.cyan.opacity(0.2)).frame(maxHeight: 3)
+        if standing {
+            VStack {
+                tirChart
+                Rectangle().fill(.cyan.opacity(0.2)).frame(maxHeight: 3)
+                groupedGlucoseStatsLaying
+            }
+        } else {
+            HStack(spacing: 20) {
+                standingTIRchart
+                groupedGlucose
+            }
+        }
+    }
+
+    init(
+        filter: NSDate,
+        _ highLimit: Binding<Decimal>,
+        _ lowLimit: Binding<Decimal>,
+        _ units: Binding<GlucoseUnits>,
+        _ overrideUnit: Binding<Bool>,
+        _ standing: Binding<Bool>
+    ) { _fetchRequest = FetchRequest<Readings>(
+        sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)],
+        predicate: NSPredicate(format: "glucose > 0 AND date > %@", filter)
+    )
+    _highLimit = highLimit
+    _lowLimit = lowLimit
+    _units = units
+    _overrideUnit = overrideUnit
+    _standing = standing
+    }
+
+    var glucoseChart: some View {
+        // Be aware of the low/lowLimit difference. lowLimit/highLimit is always in mg/dl, whereas low/high is configurable in settings
+        let low = lowLimit * (units == .mmolL ? Decimal(conversionFactor) : 1)
+        let high = highLimit * (units == .mmolL ? Decimal(conversionFactor) : 1)
+        let readings = fetchRequest
+        let count = readings.count
+        // The symbol size when fewer readings are larger
+        let sizeOfDataPoints: CGFloat = count < 20 ? 50 : count < 50 ? 35 : count > 2000 ? 5 : 15
+
+        return Chart {
+            ForEach(readings.filter({ $0.glucose > Int(highLimit) }), id: \.date) { item in
+                PointMark(
+                    x: .value("Date", item.date ?? Date()),
+                    y: .value("High", Double(item.glucose) * (units == .mmolL ? self.conversionFactor : 1))
+                )
+                .foregroundStyle(.orange)
+                .symbolSize(sizeOfDataPoints)
+            }
+            ForEach(
+                readings
+                    .filter({
+                        $0.glucose >= Int(lowLimit) && $0
+                            .glucose <= Int(highLimit) }),
+                id: \.date
+            ) { item in
+                PointMark(
+                    x: .value("Date", item.date ?? Date()),
+                    y: .value("In Range", Double(item.glucose) * (units == .mmolL ? conversionFactor : 1))
+                )
+                .foregroundStyle(.green)
+                .symbolSize(sizeOfDataPoints)
+            }
+            ForEach(readings.filter({ $0.glucose < Int(lowLimit) }), id: \.date) { item in
+                PointMark(
+                    x: .value("Date", item.date ?? Date()),
+                    y: .value("Low", Double(item.glucose) * (units == .mmolL ? conversionFactor : 1))
+                )
+                .foregroundStyle(.red)
+                .symbolSize(sizeOfDataPoints)
+            }
+        }
+        .chartYAxis {
+            AxisMarks(
+                values: [
+                    0,
+                    low,
+                    high,
+                    units == .mmolL ? 15 : 270
+                ]
+            )
+        }
+    }
+
+    var tirChart: some View {
+        let fetched = tir()
+        let low = lowLimit * (units == .mmolL ? Decimal(conversionFactor) : 1)
+        let high = highLimit * (units == .mmolL ? Decimal(conversionFactor) : 1)
+
+        let data: [ShapeModel] = [
+            .init(
+                type: NSLocalizedString(
+                    "Low",
+                    comment: ""
+                ) + " (≤\(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))",
+                percent: fetched[0].decimal
+            ),
+            .init(type: NSLocalizedString("In Range", comment: ""), percent: fetched[1].decimal),
+            .init(
+                type: NSLocalizedString(
+                    "High",
+                    comment: ""
+                ) + " (≥\(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))",
+                percent: fetched[2].decimal
+            )
+        ]
+        return Chart(data) { shape in
+            BarMark(
+                x: .value("TIR", shape.percent)
+            )
+            .foregroundStyle(by: .value("Group", shape.type))
+            .annotation(position: .top, alignment: .center) {
+                Text(
+                    "\(shape.percent, format: .number.precision(.fractionLength(0))) %"
+                ).font(.footnote).foregroundColor(.secondary)
+            }
+        }
+        .chartXAxis(.hidden)
+        .chartForegroundStyleScale([
+            NSLocalizedString(
+                "Low",
+                comment: ""
+            ) + " (≤\(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .red,
+            NSLocalizedString("In Range", comment: ""): .green,
+            NSLocalizedString(
+                "High",
+                comment: ""
+            ) + " (≥\(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .orange
+        ]).frame(maxHeight: 25)
+    }
+
+    var standingTIRchart: some View {
+        let fetched = tir()
+        let low = lowLimit * (units == .mmolL ? Decimal(conversionFactor) : 1)
+        let high = highLimit * (units == .mmolL ? Decimal(conversionFactor) : 1)
+        let fraction = units == .mmolL ? 1 : 0
+        let data: [ShapeModel] = [
+            .init(
+                type: NSLocalizedString(
+                    "Low",
+                    comment: ""
+                ) + " (≤ \(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))",
+                percent: fetched[0].decimal
+            ),
+            .init(
+                type: "> \(low.formatted(.number.precision(.fractionLength(fraction)))) - < \(high.formatted(.number.precision(.fractionLength(fraction))))",
+                percent: fetched[1].decimal
+            ),
+            .init(
+                type: NSLocalizedString(
+                    "High",
+                    comment: ""
+                ) + " (≥ \(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))",
+                percent: fetched[2].decimal
+            )
+        ]
+        return Chart(data) { shape in
+            BarMark(
+                x: .value("Shape", shape.type),
+                y: .value("Percentage", shape.percent)
+            )
+            .foregroundStyle(by: .value("Group", shape.type))
+            .annotation(position: shape.percent > 19 ? .overlay : .automatic, alignment: .center) {
+                Text(shape.percent == 0 ? "" : "\(shape.percent, format: .number.precision(.fractionLength(0)))")
+            }
+        }
+        .chartXAxis(.hidden)
+        .chartYAxis {
+            AxisMarks(
+                format: Decimal.FormatStyle.Percent.percent.scale(1)
+            )
+        }
+        .chartForegroundStyleScale([
+            NSLocalizedString(
+                "Low",
+                comment: ""
+            ) + " (≤ \(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .red,
+            "> \(low.formatted(.number.precision(.fractionLength(fraction)))) - < \(high.formatted(.number.precision(.fractionLength(fraction))))": .green,
+            NSLocalizedString(
+                "High",
+                comment: ""
+            ) + " (≥ \(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .orange
+        ])
+    }
+
+    var groupedGlucose: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            let glucose = fetchRequest
+            let mapGlucose = glucose.compactMap({ each in each.glucose })
+            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)
+                }
+            }.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)
+                }
+            }.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)
+                }
+            }.font(.caption)
+        }
+    }
+
+    var groupedGlucoseStatsLaying: some View {
+        HStack {
+            let glucose = fetchRequest
+
+            let mapGlucose = glucose.compactMap({ each in each.glucose })
+            let mapGlucoseLow = mapGlucose.filter({ $0 < Int16(3.3 / 0.0555) })
+            let mapGlucoseAcuteLow = mapGlucose.filter({ $0 < Int16(2.6 / 0.0555) })
+
+            let mapGlucoseHigh = mapGlucose.filter({ $0 > Int(7.8 / 0.0555) })
+            let mapGlucoseAcuteHigh = mapGlucose.filter({ $0 > Int16(11 / 0.0555) })
+
+            HStack {
+                let value = Double(mapGlucoseAcuteLow.count * 100 / mapGlucose.count)
+                if value != 0 {
+                    Text(units == .mmolL ? "< 2.6" : "< 47").font(.caption2).foregroundColor(.secondary)
+                    Text(value.formatted()).font(.caption).foregroundColor(value == 0 ? .green : .red)
+                    Text("%").font(.caption)
+                }
+            }.padding(.horizontal, 10)
+            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 : .orange)
+                    Text("%").font(.caption)
+                }
+            }
+            Spacer()
+            HStack {
+                let value = Double(mapGlucoseHigh.count * 100 / mapGlucose.count)
+                if value != 0 {
+                    Text(units == .mmolL ? "> 7.8" : "> 140").font(.caption).foregroundColor(.secondary)
+                    Text(value.formatted()).font(.caption).foregroundColor(value == 0 ? .green : .orange)
+                    Text("%").font(.caption)
+                }
+            }.padding(.horizontal, 10)
+            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 : .red)
+                    Text("%").font(.caption)
+                }
+            }
+        }
+    }
+
+    private func tir() -> [(decimal: Decimal, string: String)] {
+        let hypoLimit = Int(lowLimit)
+        let hyperLimit = Int(highLimit)
+
+        let glucose = fetchRequest
+
+        let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) })
+        let totalReadings = justGlucoseArray.count
+
+        let hyperArray = glucose.filter({ $0.glucose >= hyperLimit })
+        let hyperReadings = hyperArray.compactMap({ each in each.glucose as Int16 }).count
+        let hyperPercentage = Double(hyperReadings) / Double(totalReadings) * 100
+
+        let hypoArray = glucose.filter({ $0.glucose <= hypoLimit })
+        let hypoReadings = hypoArray.compactMap({ each in each.glucose as Int16 }).count
+        let hypoPercentage = Double(hypoReadings) / Double(totalReadings) * 100
+
+        let tir = 100 - (hypoPercentage + hyperPercentage)
+
+        var array: [(decimal: Decimal, string: String)] = []
+        array.append((decimal: Decimal(hypoPercentage), string: "Low"))
+        array.append((decimal: Decimal(tir), string: "NormaL"))
+        array.append((decimal: Decimal(hyperPercentage), string: "High"))
+
+        return array
+    }
+}

+ 93 - 738
FreeAPS/Sources/Modules/Stat/View/StatRootView.swift

@@ -10,54 +10,11 @@ extension Stat {
         @StateObject var state = StateModel()
 
         @FetchRequest(
-            entity: Readings.entity(),
-            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(
-                format: "date >= %@", Calendar.current.startOfDay(for: Date()) as NSDate
-            )
-        ) var fetchedGlucoseDay: FetchedResults<Readings>
-
-        @FetchRequest(
-            entity: Readings.entity(),
-            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)],
-            predicate: NSPredicate(format: "date > %@", Date().addingTimeInterval(-24.hours.timeInterval) as NSDate)
-        ) var fetchedGlucoseTwentyFourHours: FetchedResults<Readings>
-
-        @FetchRequest(
-            entity: Readings.entity(),
-            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)],
-            predicate: NSPredicate(format: "date > %@", Date().addingTimeInterval(-7.days.timeInterval) as NSDate)
-        ) var fetchedGlucoseWeek: FetchedResults<Readings>
-
-        @FetchRequest(
-            entity: Readings.entity(),
-            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(
-                format: "date > %@",
-                Date().addingTimeInterval(-30.days.timeInterval) as NSDate
-            )
-        ) var fetchedGlucoseMonth: FetchedResults<Readings>
-
-        @FetchRequest(
-            entity: Readings.entity(),
-            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(
-                format: "date > %@",
-                Date().addingTimeInterval(-90.days.timeInterval) as NSDate
-            )
-        ) var fetchedGlucose: FetchedResults<Readings>
-
-        @FetchRequest(
             entity: TDD.entity(),
             sortDescriptors: [NSSortDescriptor(key: "timestamp", ascending: false)]
         ) var fetchedTDD: FetchedResults<TDD>
 
         @FetchRequest(
-            entity: LoopStatRecord.entity(),
-            sortDescriptors: [NSSortDescriptor(key: "start", ascending: false)], predicate: NSPredicate(
-                format: "start > %@",
-                Date().addingTimeInterval(-24.hours.timeInterval) as NSDate
-            )
-        ) var fetchedLoopStats: FetchedResults<LoopStatRecord>
-
-        @FetchRequest(
             entity: InsulinDistribution.entity(),
             sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
         ) var fetchedInsulin: FetchedResults<InsulinDistribution>
@@ -79,722 +36,120 @@ extension Stat {
         @State var conversionFactor = 0.0555
 
         @ViewBuilder func stats() -> some View {
-            if state.layingChart ?? true {
-                bloodGlucose
-                Divider()
-            } else {
-                bloodGlucose
-                Divider()
-                standingTIRchart
-                Divider()
+            ZStack {
+                Color.gray.opacity(0.05).ignoresSafeArea(.all)
+                let filter = DateFilter()
+                switch selectedDuration {
+                case .Today:
+                    StatsView(
+                        filter: filter.today,
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
+                    )
+                case .Day:
+                    StatsView(
+                        filter: filter.day,
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
+                    )
+                case .Week:
+                    StatsView(
+                        filter: filter.week,
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
+                    )
+                case .Month:
+                    StatsView(
+                        filter: filter.month,
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
+                    )
+                case .Total:
+                    StatsView(
+                        filter: filter.total,
+                        $state.highLimit,
+                        $state.lowLimit,
+                        $state.units,
+                        $state.overrideUnit
+                    )
+                }
             }
-            loops
-            Divider()
-            hba1c
         }
 
         @ViewBuilder func chart() -> some View {
+            let filter = DateFilter()
             switch selectedDuration {
             case .Today:
-                glucoseChart
+                ChartsView(
+                    filter: filter.today,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart
+                )
             case .Day:
-                glucoseChartTwentyFourHours
+                ChartsView(
+                    filter: filter.day,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart
+                )
             case .Week:
-                glucoseChartWeek
+                ChartsView(
+                    filter: filter.week,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart
+                )
             case .Month:
-                glucoseChartMonth
+                ChartsView(
+                    filter: filter.month,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart
+                )
             case .Total:
-                glucoseChart90
-            }
-            if state.layingChart ?? true {
-                tirChart
+                ChartsView(
+                    filter: filter.total,
+                    $state.highLimit,
+                    $state.lowLimit,
+                    $state.units,
+                    $state.overrideUnit,
+                    $state.layingChart
+                )
             }
         }
 
         var body: some View {
-            ZStack {
-                VStack(alignment: .center) {
-                    chart().padding(.top, 20)
-                    Divider()
-                    stats()
-                    Divider()
-                    Picker("Duration", selection: $selectedDuration) {
-                        ForEach(Duration.allCases) { duration in
-                            Text(NSLocalizedString(duration.rawValue, comment: "")).tag(Optional(duration))
-                        }
+            VStack(alignment: .center) {
+                chart().padding(.top, 20)
+                Picker("Duration", selection: $selectedDuration) {
+                    ForEach(Duration.allCases) { duration in
+                        Text(NSLocalizedString(duration.rawValue, comment: "")).tag(Optional(duration))
                     }
-                    .pickerStyle(.segmented)
                 }
+                .pickerStyle(.segmented).background(.cyan.opacity(0.2))
+                stats()
             }
             .onAppear(perform: configureView)
             .navigationBarTitle("Statistics")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(leading: Button("Close", action: state.hideModal))
         }
-
-        var loops: some View {
-            VStack {
-                let loops_ = loopStats(fetchedLoopStats)
-                HStack {
-                    ForEach(0 ..< loops_.count, id: \.self) { index in
-                        VStack {
-                            Text(NSLocalizedString(loops_[index].string, comment: "")).font(.subheadline)
-                                .foregroundColor(.secondary)
-                            Text(
-                                index == 0 ? loops_[index].double.formatted() : (
-                                    index == 2 ? loops_[index].double
-                                        .formatted(.number.grouping(.never).rounded().precision(.fractionLength(2))) :
-                                        loops_[index]
-                                        .double
-                                        .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))
-                                )
-                            )
-                        }.padding(.horizontal, 6)
-                    }
-                }
-            }
-        }
-
-        var hba1c: some View {
-            let useUnit: GlucoseUnits = (state.units == .mmolL && (state.overrideUnit ?? false)) ? .mgdL :
-                (state.units == .mgdL && (state.overrideUnit ?? false) || state.units == .mmolL) ? .mmolL : .mgdL
-            return HStack {
-                let hba1cs = glucoseStats(fetchedGlucose)
-                let hba1cString = (
-                    useUnit == .mmolL ? hba1cs.ifcc
-                        .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) : hba1cs.ngsp
-                        .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))
-                        + " %"
-                )
-
-                VStack {
-                    Text("HbA1C").font(.subheadline).foregroundColor(headline)
-                    HStack {
-                        VStack {
-                            Text(hba1cString)
-                        }
-                    }
-                }.padding([.horizontal], 15)
-                VStack {
-                    Text("SD").font(.subheadline).foregroundColor(.secondary)
-                    HStack {
-                        VStack {
-                            Text(
-                                hba1cs.sd
-                                    .formatted(
-                                        .number.grouping(.never).rounded()
-                                            .precision(.fractionLength(state.units == .mmolL ? 1 : 0))
-                                    )
-                            )
-                        }
-                    }
-                }.padding([.horizontal], 15)
-                VStack {
-                    Text("CV").font(.subheadline).foregroundColor(.secondary)
-                    HStack {
-                        VStack {
-                            Text(
-                                hba1cs.cv.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))
-                            )
-                        }
-                    }
-                }.padding([.horizontal], 15)
-                // if selectedDuration == .Total || selectedDuration == .Today {
-                VStack {
-                    Text("Days").font(.subheadline).foregroundColor(.secondary)
-                    HStack {
-                        VStack {
-                            Text(numberOfDays.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))))
-                        }
-                    }
-                }.padding([.horizontal], 15)
-                // }
-            }
-        }
-
-        var bloodGlucose: some View {
-            VStack {
-                HStack {
-                    let bgs = glucoseStats(fetchedGlucose)
-                    VStack {
-                        HStack {
-                            Text(selectedDuration == .Today ? "Readings today" : "Readings / 24h").font(.subheadline)
-                                .foregroundColor(.secondary)
-                        }
-                        HStack {
-                            VStack {
-                                Text(
-                                    bgs.readings.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))
-                                )
-                            }
-                        }
-                    }
-                    VStack {
-                        HStack {
-                            Text("Average").font(.subheadline).foregroundColor(headline)
-                        }
-                        HStack {
-                            VStack {
-                                Text(
-                                    bgs.average
-                                        .formatted(
-                                            .number.grouping(.never).rounded()
-                                                .precision(.fractionLength(state.units == .mmolL ? 1 : 0))
-                                        )
-                                )
-                            }
-                        }
-                    }
-                    VStack {
-                        HStack {
-                            Text("Median").font(.subheadline).foregroundColor(.secondary)
-                        }
-                        HStack {
-                            VStack {
-                                Text(
-                                    bgs.median
-                                        .formatted(
-                                            .number.grouping(.never).rounded()
-                                                .precision(.fractionLength(state.units == .mmolL ? 1 : 0))
-                                        )
-                                )
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        var numberOfDays: Double {
-            let array = selectedDuration == .Today ? fetchedGlucoseDay : selectedDuration == .Day ?
-                fetchedGlucoseTwentyFourHours :
-                selectedDuration == .Week ? fetchedGlucoseWeek : selectedDuration == .Month ? fetchedGlucoseMonth :
-                selectedDuration ==
-                .Total ? fetchedGlucose : fetchedGlucoseDay
-
-            let endIndex = array.count - 1
-            var days = 0.0
-
-            if endIndex > 0 {
-                let firstElementTime = fetchedGlucose.first?.date ?? Date()
-                let lastElementTime = fetchedGlucose[endIndex].date ?? Date()
-                days = (firstElementTime - lastElementTime).timeInterval / 8.64E4
-            }
-            return days
-        }
-
-        var tirChart: some View {
-            let array = selectedDuration == .Today ? fetchedGlucoseDay : selectedDuration == .Day ?
-                fetchedGlucoseTwentyFourHours :
-                selectedDuration == .Week ? fetchedGlucoseWeek : selectedDuration == .Month ? fetchedGlucoseMonth :
-                selectedDuration ==
-                .Total ? fetchedGlucose : fetchedGlucoseDay
-            let fetched = tir(array)
-            let data: [ShapeModel] = [
-                .init(type: NSLocalizedString("Low", comment: ""), percent: fetched[0].decimal),
-                .init(type: NSLocalizedString("In Range", comment: ""), percent: fetched[1].decimal),
-                .init(type: NSLocalizedString("High", comment: ""), percent: fetched[2].decimal)
-            ]
-
-            return Chart(data) { shape in
-                BarMark(
-                    x: .value("TIR", shape.percent)
-                )
-                .foregroundStyle(by: .value("Group", shape.type))
-                .annotation(position: .overlay, alignment: .center) {
-                    Text(
-                        shape.percent == 0 ? "" : shape
-                            .percent < 12 ? "\(shape.percent, format: .number.precision(.fractionLength(0)))" :
-                            "\(shape.percent, format: .number.precision(.fractionLength(0))) %"
-                    )
-                }
-            }
-            .chartXAxis(.hidden)
-            .chartForegroundStyleScale([
-                NSLocalizedString("Low", comment: ""): .red,
-                NSLocalizedString("In Range", comment: ""): .green,
-                NSLocalizedString("High", comment: ""): .orange
-            ]).frame(maxHeight: 55)
-        }
-
-        var standingTIRchart: some View {
-            let array = selectedDuration == .Today ? fetchedGlucoseDay : selectedDuration == .Day ?
-                fetchedGlucoseTwentyFourHours :
-                selectedDuration == .Week ? fetchedGlucoseWeek : selectedDuration == .Month ? fetchedGlucoseMonth :
-                selectedDuration == .Total ? fetchedGlucose : fetchedGlucoseDay
-            let fetched = tir(array)
-            let data: [ShapeModel] = [
-                .init(type: NSLocalizedString("Low", comment: ""), percent: fetched[0].decimal),
-                .init(type: NSLocalizedString("In Range", comment: ""), percent: fetched[1].decimal),
-                .init(type: NSLocalizedString("High", comment: ""), percent: fetched[2].decimal)
-            ]
-
-            return VStack(alignment: .center) {
-                Chart(data) { shape in
-                    BarMark(
-                        x: .value("Shape", shape.type),
-                        y: .value("Percentage", shape.percent)
-                    )
-                    .foregroundStyle(by: .value("Group", shape.type))
-                    .annotation(position: shape.percent <= 9 ? .top : .overlay, alignment: .center) {
-                        Text(shape.percent == 0 ? "" : "\(shape.percent, format: .number.precision(.fractionLength(0))) %")
-                    }
-                }
-                .chartYAxis(.hidden)
-                .chartLegend(.hidden)
-                .chartForegroundStyleScale([
-                    NSLocalizedString("Low", comment: ""): .red,
-                    NSLocalizedString("In Range", comment: ""): .green,
-                    NSLocalizedString("High", comment: ""): .orange
-                ])
-            }
-        }
-
-        var glucoseChart: some View {
-            let count = fetchedGlucoseDay.count
-            let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            return Chart {
-                ForEach(fetchedGlucoseDay.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.orange)
-                    .symbolSize(count < 20 ? 30 : 12)
-                }
-                ForEach(
-                    fetchedGlucoseDay
-                        .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
-                    id: \.date
-                ) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.green)
-                    .symbolSize(count < 20 ? 30 : 12)
-                }
-                ForEach(fetchedGlucoseDay.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.red)
-                    .symbolSize(count < 20 ? 30 : 12)
-                }
-            }
-            .chartYAxis {
-                AxisMarks(
-                    values: [
-                        0,
-                        lowLimit,
-                        highLimit,
-                        state.units == .mmolL ? 15 : 270
-                    ]
-                )
-            }
-        }
-
-        var glucoseChartTwentyFourHours: some View {
-            let count = fetchedGlucoseTwentyFourHours.count
-            let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            return Chart {
-                ForEach(fetchedGlucoseTwentyFourHours.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.orange)
-                    .symbolSize(count < 20 ? 20 : 10)
-                }
-                ForEach(
-                    fetchedGlucoseTwentyFourHours
-                        .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
-                    id: \.date
-                ) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.green)
-                    .symbolSize(count < 20 ? 20 : 10)
-                }
-                ForEach(fetchedGlucoseTwentyFourHours.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.red)
-                    .symbolSize(count < 20 ? 20 : 10)
-                }
-            }
-            .chartYAxis {
-                AxisMarks(
-                    values: [
-                        0,
-                        lowLimit,
-                        highLimit,
-                        state.units == .mmolL ? 15 : 270
-                    ]
-                )
-            } }
-
-        var glucoseChartWeek: some View {
-            let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            return Chart {
-                ForEach(fetchedGlucoseWeek.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.orange)
-                    .symbolSize(5)
-                }
-                ForEach(
-                    fetchedGlucoseWeek
-                        .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
-                    id: \.date
-                ) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.green)
-                    .symbolSize(5)
-                }
-                ForEach(fetchedGlucoseWeek.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.red)
-                    .symbolSize(5)
-                }
-            }
-            .chartYAxis {
-                AxisMarks(
-                    values: [
-                        0,
-                        lowLimit,
-                        highLimit,
-                        state.units == .mmolL ? 15 : 270
-                    ]
-                )
-            }
-        }
-
-        var glucoseChartMonth: some View {
-            let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            return Chart {
-                ForEach(fetchedGlucoseMonth.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.orange)
-                    .symbolSize(2)
-                }
-                ForEach(
-                    fetchedGlucoseMonth
-                        .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
-                    id: \.date
-                ) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.green)
-                    .symbolSize(2)
-                }
-                ForEach(fetchedGlucoseMonth.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.red)
-                    .symbolSize(2)
-                }
-            }
-            .chartYAxis {
-                AxisMarks(
-                    values: [
-                        0,
-                        lowLimit,
-                        highLimit,
-                        state.units == .mmolL ? 15 : 270
-                    ]
-                )
-            }
-        }
-
-        var glucoseChart90: some View {
-            let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
-            return Chart {
-                ForEach(fetchedGlucose.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.orange)
-                    .symbolSize(2)
-                }
-                ForEach(
-                    fetchedGlucose
-                        .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
-                    id: \.date
-                ) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.green)
-                    .symbolSize(2)
-                }
-                ForEach(fetchedGlucose.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
-                    PointMark(
-                        x: .value("Date", item.date ?? Date()),
-                        y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
-                    )
-                    .foregroundStyle(.red)
-                    .symbolSize(2)
-                }
-            }
-            .chartYAxis {
-                AxisMarks(
-                    values: [
-                        0,
-                        lowLimit,
-                        highLimit,
-                        state.units == .mmolL ? 15 : 270
-                    ]
-                )
-            }
-        }
-
-        private func loopStats(_ loops: FetchedResults<LoopStatRecord>) -> [(double: Double, string: String)] {
-            guard (loops.first?.start) != nil else { return [] }
-
-            var i = 0.0
-            var minimumInt = 999.0
-            var maximumInt = 0.0
-            var timeIntervalLoops = 0.0
-            var previousTimeLoop = loops.first?.end ?? Date()
-            var timeIntervalLoopArray: [Double] = []
-
-            let durationArray = loops.compactMap({ each in each.duration })
-            let durationArrayCount = durationArray.count
-            // var durationAverage = durationArray.reduce(0, +) / Double(durationArrayCount)
-
-            let medianDuration = medianCalculationDouble(array: durationArray)
-            let successsNR = loops.compactMap({ each in each.loopStatus }).filter({ each in each!.contains("Success") }).count
-            let errorNR = durationArrayCount - successsNR
-            let successRate: Double? = (Double(successsNR) / Double(successsNR + errorNR)) * 100
-
-            for each in loops {
-                if let loopEnd = each.end {
-                    i += 1
-                    timeIntervalLoops = (previousTimeLoop - (each.start ?? previousTimeLoop)).timeInterval / 60
-
-                    if timeIntervalLoops > 0.0, i != 1 {
-                        timeIntervalLoopArray.append(timeIntervalLoops)
-                    }
-                    if timeIntervalLoops > maximumInt {
-                        maximumInt = timeIntervalLoops
-                    }
-                    if timeIntervalLoops < minimumInt, i != 1 {
-                        minimumInt = timeIntervalLoops
-                    }
-                    previousTimeLoop = loopEnd
-                }
-            }
-
-            // Average Loop Interval in minutes
-            let timeOfFirstIndex = loops.first?.start ?? Date()
-            let lastIndexWithTimestamp = loops.count - 1
-            let timeOfLastIndex = loops[lastIndexWithTimestamp].end ?? Date()
-            let averageInterval = (timeOfFirstIndex - timeOfLastIndex).timeInterval / 60 / Double(errorNR + successsNR)
-
-            if minimumInt == 999.0 {
-                minimumInt = 0.0
-            }
-
-            var array: [(double: Double, string: String)] = []
-
-            array.append((double: Double(successsNR + errorNR), string: "Loops"))
-            array.append((double: averageInterval, string: "Interval"))
-            array.append((double: medianDuration, string: "Duration"))
-            array.append((double: successRate ?? 100, string: "%"))
-
-            return array
-        }
-
-        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 medianCalculationDouble(array: [Double]) -> Double {
-            guard !array.isEmpty else {
-                return 0
-            }
-            let sorted = array.sorted()
-            let length = array.count
-
-            if length % 2 == 0 {
-                return (sorted[length / 2 - 1] + sorted[length / 2]) / 2
-            }
-            return sorted[length / 2]
-        }
-
-        private func glucoseStats(_ glucose_90: FetchedResults<Readings>)
-            -> (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)
-        {
-            var numberOfDays: Double = 0
-            let endIndex = glucose_90.count - 1
-
-            if endIndex > 0 {
-                let firstElementTime = glucose_90[0].date ?? Date()
-                let lastElementTime = glucose_90[endIndex].date ?? Date()
-                numberOfDays = (firstElementTime - lastElementTime).timeInterval / 8.64E4
-            }
-            var duration = 1
-            var denominator: Double = 1
-
-            switch selectedDuration {
-            case .Today:
-                let minutesSinceMidnight = Calendar.current.component(.hour, from: Date()) * 60 + Calendar.current
-                    .component(.minute, from: Date())
-                duration = minutesSinceMidnight
-                denominator = 1
-            case .Day:
-                duration = 1 * 1440
-                denominator = 1
-            case .Week:
-                duration = 7 * 1440
-                if numberOfDays > 7 { denominator = 7 } else { denominator = numberOfDays }
-            case .Month:
-                duration = 30 * 1440
-                if numberOfDays > 30 { denominator = 30 } else { denominator = numberOfDays }
-            case .Total:
-                duration = 90 * 1440
-                if numberOfDays >= 90 { denominator = 90 } else { denominator = numberOfDays }
-            }
-
-            let timeAgo = Date().addingTimeInterval(-duration.minutes.timeInterval)
-            let glucose = glucose_90.filter({ ($0.date ?? Date()) >= timeAgo })
-
-            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
-
-            if numberOfDays > 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
-            }
-
-            var output: (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)
-            output = (
-                ifcc: IFCCa1CStatisticValue,
-                ngsp: NGSPa1CStatisticValue,
-                average: glucoseAverage * (state.units == .mmolL ? conversionFactor : 1),
-                median: medianGlucose * (state.units == .mmolL ? conversionFactor : 1),
-                sd: sd * (state.units == .mmolL ? conversionFactor : 1), cv: cv,
-                readings: Double(countReadings) / denominator
-            )
-            return output
-        }
-
-        private func tir(_ glucose_90: FetchedResults<Readings>) -> [(decimal: Decimal, string: String)] {
-            var duration = 1
-
-            switch selectedDuration {
-            case .Today:
-                let minutesSinceMidnight = Calendar.current.component(.hour, from: Date()) * 60 + Calendar.current
-                    .component(.minute, from: Date())
-                duration = minutesSinceMidnight
-            case .Day:
-                duration = 1 * 1440
-            case .Week:
-                duration = 7 * 1440
-            case .Month:
-                duration = 30 * 1440
-            case .Total:
-                duration = 90 * 1440
-            }
-
-            let hypoLimit = Int(state.lowLimit ?? 70)
-            let hyperLimit = Int(state.highLimit ?? 145)
-
-            let timeAgo = Date().addingTimeInterval(-duration.minutes.timeInterval)
-            let glucose = glucose_90.filter({ ($0.date ?? Date()) >= timeAgo })
-
-            let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) })
-            let totalReadings = justGlucoseArray.count
-
-            let hyperArray = glucose.filter({ $0.glucose >= hyperLimit })
-            let hyperReadings = hyperArray.compactMap({ each in each.glucose as Int16 }).count
-            let hyperPercentage = Double(hyperReadings) / Double(totalReadings) * 100
-
-            let hypoArray = glucose.filter({ $0.glucose <= hypoLimit })
-            let hypoReadings = hypoArray.compactMap({ each in each.glucose as Int16 }).count
-            let hypoPercentage = Double(hypoReadings) / Double(totalReadings) * 100
-
-            let tir = 100 - (hypoPercentage + hyperPercentage)
-
-            var array: [(decimal: Decimal, string: String)] = []
-            array.append((decimal: Decimal(hypoPercentage), string: "Low"))
-            array.append((decimal: Decimal(tir), string: "NormaL"))
-            array.append((decimal: Decimal(hyperPercentage), string: "High"))
-
-            return array
-        }
-
-        private func colorOfGlucose(_ index: Int) -> Color {
-            let whichIndex = index
-
-            switch whichIndex {
-            case 0:
-                return .red
-            case 1:
-                return .green
-            case 2:
-                return .orange
-            default:
-                return .primary
-            }
-        }
     }
 }

+ 299 - 0
FreeAPS/Sources/Modules/Stat/View/StatsView.swift

@@ -0,0 +1,299 @@
+//
+//  FilteredLoopsView.swift
+//  FreeAPS
+//
+//  Created by Jon Mårtensson on 2023-05-29.
+//
+import CoreData
+import SwiftDate
+import SwiftUI
+
+struct StatsView: View {
+    @FetchRequest var fetchRequest: FetchedResults<LoopStatRecord>
+    @FetchRequest var fetchRequestReadings: FetchedResults<Readings>
+
+    @State var headline: Color = .secondary
+
+    @Binding var highLimit: Decimal
+    @Binding var lowLimit: Decimal
+    @Binding var units: GlucoseUnits
+    @Binding var overrideUnit: Bool
+
+    private let conversionFactor = 0.0555
+
+    var body: some View {
+        VStack(spacing: 10) {
+            loops
+            Divider()
+            hba1c
+            Divider()
+            bloodGlucose
+        }
+    }
+
+    init(
+        filter: NSDate,
+        _ highLimit: Binding<Decimal>,
+        _ lowLimit: Binding<Decimal>,
+        _ units: Binding<GlucoseUnits>,
+        _ overrideUnit: Binding<Bool>
+    ) {
+        _fetchRequest = FetchRequest<LoopStatRecord>(
+            sortDescriptors: [NSSortDescriptor(key: "start", ascending: false)],
+            predicate: NSPredicate(format: "interval > 0 AND start > %@", filter)
+        )
+
+        _fetchRequestReadings = FetchRequest<Readings>(
+            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)],
+            predicate: NSPredicate(format: "glucose > 0 AND date > %@", filter)
+        )
+
+        _highLimit = highLimit
+        _lowLimit = lowLimit
+        _units = units
+        _overrideUnit = overrideUnit
+    }
+
+    var loops: some View {
+        VStack(spacing: 10) {
+            let loops = fetchRequest
+            if !loops.isEmpty {
+                // 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
+                // var durationAverage = durationArray.reduce(0, +) / Double(durationArrayCount)
+                let medianDuration = medianCalculationDouble(array: durationArray)
+                let successsNR = loops.compactMap({ each in each.loopStatus }).filter({ each in each!.contains("Success") })
+                    .count
+                let errorNR = durationArrayCount - successsNR
+                let successRate: Double? = (Double(successsNR) / Double(successsNR + errorNR)) * 100
+
+                let loopNr = totalTime <= 1 ? Double(successsNR + errorNR) : round(Double(successsNR + errorNR) / totalTime)
+
+                let intervalArray = loops.compactMap({ each in each.interval as Double })
+                let intervalAverage = intervalArray.reduce(0, +) / Double(intervalArray.count)
+                // let maximumInterval = intervalArray.max()
+                // let minimumInterval = intervalArray.min()
+
+                HStack(spacing: 35) {
+                    VStack(spacing: 5) {
+                        Text("Loops").font(.subheadline).foregroundColor(headline)
+                        Text(loopNr.formatted())
+                    }
+                    VStack(spacing: 5) {
+                        Text("Interval").font(.subheadline).foregroundColor(headline)
+                        Text(intervalAverage.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) + " min")
+                    }
+                    VStack(spacing: 5) {
+                        Text("Duration").font(.subheadline).foregroundColor(headline)
+                        Text(
+                            (medianDuration * 60)
+                                .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) + " s"
+                        )
+                    }
+                    VStack(spacing: 5) {
+                        Text("Sucess").font(.subheadline).foregroundColor(headline)
+                        Text(
+                            ((successRate ?? 100) / 100)
+                                .formatted(.percent.grouping(.never).rounded().precision(.fractionLength(1)))
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    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 medianCalculationDouble(array: [Double]) -> Double {
+        guard !array.isEmpty else {
+            return 0
+        }
+        let sorted = array.sorted()
+        let length = array.count
+
+        if length % 2 == 0 {
+            return (sorted[length / 2 - 1] + sorted[length / 2]) / 2
+        }
+        return sorted[length / 2]
+    }
+
+    var hba1c: some View {
+        HStack(spacing: 50) {
+            let useUnit: GlucoseUnits = (units == .mmolL && overrideUnit) ? .mgdL :
+                (units == .mgdL && overrideUnit || units == .mmolL) ? .mmolL : .mgdL
+            let hba1cs = glucoseStats()
+            let glucose = fetchRequestReadings
+            // 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
+
+            let hba1cString = (
+                useUnit == .mmolL ? hba1cs.ifcc
+                    .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) : hba1cs.ngsp
+                    .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))
+                    + " %"
+            )
+            VStack(spacing: 5) {
+                Text("HbA1C").font(.subheadline).foregroundColor(headline)
+                Text(hba1cString)
+            }
+            VStack(spacing: 5) {
+                Text("SD").font(.subheadline).foregroundColor(.secondary)
+                Text(
+                    hba1cs.sd
+                        .formatted(
+                            .number.grouping(.never).rounded()
+                                .precision(.fractionLength(units == .mmolL ? 1 : 0))
+                        )
+                )
+            }
+            VStack(spacing: 5) {
+                Text("CV").font(.subheadline).foregroundColor(.secondary)
+                Text(hba1cs.cv.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))))
+            }
+            VStack(spacing: 5) {
+                Text("Days").font(.subheadline).foregroundColor(.secondary)
+                Text(numberOfDays.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))))
+            }
+        }
+    }
+
+    var bloodGlucose: some View {
+        HStack(spacing: 30) {
+            let bgs = glucoseStats()
+
+            let glucose = fetchRequestReadings
+            // 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
+
+            VStack(spacing: 5) {
+                Text(numberOfDays < 1 ? "Readings today" : "Readings / 24h").font(.subheadline)
+                    .foregroundColor(.secondary)
+                Text(bgs.readings.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))))
+            }
+            VStack(spacing: 5) {
+                Text("Average").font(.subheadline).foregroundColor(headline)
+                Text(
+                    bgs.average
+                        .formatted(
+                            .number.grouping(.never).rounded()
+                                .precision(.fractionLength(units == .mmolL ? 1 : 0))
+                        )
+                )
+            }
+            VStack(spacing: 5) {
+                Text("Median").font(.subheadline).foregroundColor(.secondary)
+                Text(
+                    bgs.median
+                        .formatted(
+                            .number.grouping(.never).rounded()
+                                .precision(.fractionLength(units == .mmolL ? 1 : 0))
+                        )
+                )
+            }
+        }
+    }
+
+    private func glucoseStats()
+        -> (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)
+    {
+        let glucose = fetchRequestReadings
+        // 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
+
+        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
+
+        if numberOfDays > 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
+        }
+
+        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 tir() -> [(decimal: Decimal, string: String)] {
+        let glucose = fetchRequestReadings
+        let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) })
+        let totalReadings = justGlucoseArray.count
+
+        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
+
+        let tir = 100 - (hypoPercentage + hyperPercentage)
+
+        var array: [(decimal: Decimal, string: String)] = []
+        array.append((decimal: Decimal(hypoPercentage), string: "Low"))
+        array.append((decimal: Decimal(tir), string: "NormaL"))
+        array.append((decimal: Decimal(hyperPercentage), string: "High"))
+
+        return array
+    }
+}

+ 2 - 2
FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorDataFlow.swift

@@ -9,9 +9,9 @@ enum TargetsEditor {
         var highIndex = 0
         var timeIndex = 0
 
-        init(lowIndex: Int, highIndex: Int, timeIndex: Int) {
+        init(lowIndex: Int, highIndex _: Int, timeIndex: Int) {
             self.lowIndex = lowIndex
-            self.highIndex = highIndex
+            highIndex = lowIndex
             self.timeIndex = timeIndex
         }
 

+ 3 - 3
FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift

@@ -28,7 +28,7 @@ extension TargetsEditor {
             items = profile.targets.map { value in
                 let timeIndex = timeValues.firstIndex(of: Double(value.offset * 60)) ?? 0
                 let lowIndex = rateValues.firstIndex(of: Double(value.low)) ?? 0
-                let highIndex = rateValues.firstIndex(of: Double(value.high)) ?? 0
+                let highIndex = lowIndex
                 return Item(lowIndex: lowIndex, highIndex: highIndex, timeIndex: timeIndex)
             }
         }
@@ -40,7 +40,7 @@ extension TargetsEditor {
             if let last = items.last {
                 time = last.timeIndex + 1
                 low = last.lowIndex
-                high = last.highIndex
+                high = low
             }
 
             let newItem = Item(lowIndex: low, highIndex: high, timeIndex: time)
@@ -56,7 +56,7 @@ extension TargetsEditor {
                 let date = Date(timeIntervalSince1970: self.timeValues[item.timeIndex])
                 let minutes = Int(date.timeIntervalSince1970 / 60)
                 let low = Decimal(self.rateValues[item.lowIndex])
-                let high = Decimal(self.rateValues[item.highIndex])
+                let high = low
                 return BGTargetEntry(low: low, high: high, start: fotmatter.string(from: date), offset: minutes)
             }
             let profile = BGTargets(units: units, userPrefferedUnits: settingsManager.settings.units, targets: targets)

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

@@ -54,8 +54,7 @@ extension TargetsEditor {
             GeometryReader { geometry in
                 VStack {
                     HStack {
-                        Text("Low target").frame(width: geometry.size.width / 3)
-                        Text("High target").frame(width: geometry.size.width / 3)
+                        Text("Target").frame(width: geometry.size.width / 3)
                         Text("Time").frame(width: geometry.size.width / 3)
                     }
                     HStack(spacing: 0) {
@@ -69,17 +68,6 @@ extension TargetsEditor {
                         }
                         .frame(maxWidth: geometry.size.width / 3)
                         .clipped()
-                        Picker(selection: $state.items[index].highIndex, label: EmptyView()) {
-                            ForEach(0 ..< state.rateValues.count, id: \.self) { i in
-                                Text(
-                                    self.rateFormatter
-                                        .string(from: state.rateValues[i] as NSNumber) ?? ""
-                                ).tag(i)
-                            }
-                        }
-                        .frame(maxWidth: geometry.size.width / 3)
-                        .clipped()
-
                         Picker(selection: $state.items[index].timeIndex, label: EmptyView()) {
                             ForEach(0 ..< state.timeValues.count, id: \.self) { i in
                                 Text(
@@ -106,10 +94,6 @@ extension TargetsEditor {
                             Text(
                                 "\(rateFormatter.string(from: state.rateValues[item.lowIndex] as NSNumber) ?? "0")"
                             )
-                            Text("–").foregroundColor(.secondary)
-                            Text(
-                                "\(rateFormatter.string(from: state.rateValues[item.highIndex] as NSNumber) ?? "0")"
-                            )
                             Text("\(state.units.rawValue)").foregroundColor(.secondary)
                             Spacer()
                             Text("starts at").foregroundColor(.secondary)

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

@@ -7,6 +7,7 @@ enum AwConfig: String, JSON, CaseIterable, Identifiable, Codable {
     case BGTarget
     case steps
     case isf
+    case override
 
     var displayName: String {
         switch self {
@@ -18,6 +19,8 @@ enum AwConfig: String, JSON, CaseIterable, Identifiable, Codable {
             return NSLocalizedString("Steps", comment: "")
         case .isf:
             return NSLocalizedString("ISF", comment: "")
+        case .override:
+            return NSLocalizedString("% Override", comment: "")
         }
     }
 }

+ 18 - 0
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -1,3 +1,4 @@
+import CoreData
 import Foundation
 import Swinject
 import WatchConnectivity
@@ -18,6 +19,8 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
     @Injected() private var tempTargetsStorage: TempTargetsStorage!
     @Injected() private var garmin: GarminManager!
 
+    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext // newBackgroundContext()
+
     private var lifetime = Lifetime()
 
     init(resolver: Resolver, session: WCSession = .default) {
@@ -102,6 +105,21 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
 
             self.state.isf = self.suggestion?.isf
 
+            var overrideArray = [Override]()
+            let requestOverrides = Override.fetchRequest() as NSFetchRequest<Override>
+            let sortOverride = NSSortDescriptor(key: "date", ascending: false)
+            requestOverrides.sortDescriptors = [sortOverride]
+            requestOverrides.fetchLimit = 1
+            try? overrideArray = self.coredataContext.fetch(requestOverrides)
+
+            if overrideArray.first?.enabled ?? false {
+                let percentString = "\((overrideArray.first?.percentage ?? 100).formatted(.number)) %"
+                self.state.override = percentString
+
+            } else {
+                self.state.override = "100 %"
+            }
+
             self.sendState()
         }
     }

+ 1 - 0
FreeAPSWatch WatchKit Extension/DataFlow.swift

@@ -22,6 +22,7 @@ struct WatchState: Codable {
     var eventualBGRaw: String?
     var displayOnWatch: AwConfig?
     var isf: Decimal?
+    var override: String?
 }
 
 struct TempTargetWatchPreset: Codable, Identifiable {

+ 13 - 2
FreeAPSWatch WatchKit Extension/Views/MainView.swift

@@ -176,8 +176,8 @@ struct MainView: View {
                         Image(systemName: "arrow.up.arrow.down")
                             .renderingMode(.template)
                             .resizable()
-                            .frame(width: 16, height: 16)
-                            .foregroundColor(.blue)
+                            .frame(width: 12, height: 12)
+                            .foregroundColor(.loopGreen)
                         Text("\(isf)")
                             .fontWeight(.regular)
                             .font(.caption2)
@@ -185,6 +185,17 @@ struct MainView: View {
                             .foregroundColor(.white)
                             .minimumScaleFactor(0.5)
                     }
+                case .override:
+                    Spacer()
+                    let override: String = state.override != nil ? state.override! : "-"
+                    HStack {
+                        Text("👤 \(override)")
+                            .fontWeight(.regular)
+                            .font(.caption2)
+                            .scaledToFill()
+                            .foregroundColor(.white)
+                            .minimumScaleFactor(0.5)
+                    }
                 }
             }
             Spacer()

+ 3 - 0
FreeAPSWatch WatchKit Extension/WatchStateModel.swift

@@ -9,6 +9,7 @@ enum AwConfig: String, CaseIterable, Identifiable, Codable {
     case BGTarget
     case steps
     case isf
+    case override
 }
 
 class WatchStateModel: NSObject, ObservableObject {
@@ -54,6 +55,7 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var pendingBolus: Double?
 
     @Published var isf: Decimal?
+    @Published var override: String?
 
     private var lifetime = Set<AnyCancellable>()
     private var confirmationTimeout: AnyCancellable?
@@ -172,6 +174,7 @@ class WatchStateModel: NSObject, ObservableObject {
         eventualBG = state.eventualBG ?? ""
         displayOnWatch = state.displayOnWatch ?? .BGTarget
         isf = state.isf
+        override = state.override
     }
 }