From 0194add63a3764a0d031d7fc186a6fbb737c6207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:56:15 +0200 Subject: [PATCH 01/12] Release 4.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release 4.4.0 Overdue update to the iPhone identifier list (#725) @scrappy Insulin and TDD details. (#724) @Jon-b-m Missing localizable strings. @Jon-b-m Add link to sharing to bring directly to personal statistics page (#727) @scrappy confirm pod DeliveryStatus to prevent 0x31 fault (#735) @marionbarker Complete the carbs -> bolus flow before enacting any eventual auto bo… @Jon-b-m Use NS URL for NS uploads @Jon-b-m Allow bolus when long time since last loop @Jon-b-m Don't require a NS URL for the version check @Jon-b-m Crowdin Updates. Thank you translators: Aleksandr Van-Zaam @vanzaam, @nicolevanelstvandenhoek, @Mirko-tri , Alessandro Fogliani, Hung Nguyen Phuteleco, Mykola Yroslavadudko, David de Tommas, Diabetlum and NazeehSheikh. --- Config.xcconfig | 2 +- .../CGMBLEKit/ar.lproj/Localizable.strings | 18 ++-- .../CGMBLEKitUI/ar.lproj/Localizable.strings | 20 ++--- .../ar.lproj/TransmitterManagerSetup.strings | 8 +- .../Resources/tr.lproj/Localizable.strings | 6 +- .../ar.lproj/Localizable.strings | 18 ++++ .../da.lproj/Localizable.strings | 18 ++++ .../de.lproj/Localizable.strings | 18 ++++ .../en.lproj/Localizable.strings | 18 ++++ .../es.lproj/Localizable.strings | 18 ++++ .../fi.lproj/Localizable.strings | 18 ++++ .../fr.lproj/Localizable.strings | 18 ++++ .../he.lproj/Localizable.strings | 18 ++++ .../hu.lproj/Localizable.strings | 18 ++++ .../it.lproj/Localizable.strings | 18 ++++ .../nb.lproj/Localizable.strings | 18 ++++ .../nl.lproj/Localizable.strings | 20 ++++- .../pl.lproj/Localizable.strings | 18 ++++ .../pt-BR.lproj/Localizable.strings | 18 ++++ .../pt-PT.lproj/Localizable.strings | 18 ++++ .../ru.lproj/Localizable.strings | 18 ++++ .../sk.lproj/Localizable.strings | 18 ++++ .../sv.lproj/Localizable.strings | 18 ++++ .../tr.lproj/Localizable.strings | 18 ++++ .../uk.lproj/Localizable.strings | 20 ++++- .../vi.lproj/Localizable.strings | 20 ++++- .../zh-Hans.lproj/Localizable.strings | 20 ++++- .../OmniBLE/OmniBLE/OmnipodCommon/Pod.swift | 13 ++- .../PumpManager/OmniBLEPumpManager.swift | 8 +- .../OmniBLE/PumpManager/PodCommsSession.swift | 12 +-- .../OmniKit/OmniKit/OmnipodCommon/Pod.swift | 13 ++- .../PumpManager/OmnipodPumpManager.swift | 8 +- .../OmniKit/PumpManager/PodCommsSession.swift | 12 +-- .../javascript/bundle/glucose-get-last.js | 2 +- .../javascript/prepare/determine-basal.js | 6 +- .../javascript/prepare/middleware.js | 11 ++- FreeAPS/Sources/APS/APSManager.swift | 42 +++++++++ FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift | 22 +++-- FreeAPS/Sources/Application/FreeAPSApp.swift | 1 + .../Main/ar.lproj/Localizable.strings | 9 ++ .../Main/da.lproj/Localizable.strings | 9 ++ .../Main/de.lproj/Localizable.strings | 9 ++ .../Main/en.lproj/Localizable.strings | 9 ++ .../Main/es.lproj/Localizable.strings | 9 ++ .../Main/fi.lproj/Localizable.strings | 9 ++ .../Main/fr.lproj/Localizable.strings | 11 ++- .../Main/he.lproj/Localizable.strings | 9 ++ .../Main/hu.lproj/Localizable.strings | 9 ++ .../Main/it.lproj/Localizable.strings | 9 ++ .../Main/nb.lproj/Localizable.strings | 9 ++ .../Main/nl.lproj/Localizable.strings | 9 ++ .../Main/pl.lproj/Localizable.strings | 9 ++ .../Main/pt-BR.lproj/Localizable.strings | 9 ++ .../Main/pt-PT.lproj/Localizable.strings | 9 ++ .../Main/ru.lproj/Localizable.strings | 9 ++ .../Main/sk.lproj/Localizable.strings | 9 ++ .../Main/sv.lproj/Localizable.strings | 9 ++ .../Main/tr.lproj/Localizable.strings | 9 ++ .../Main/uk.lproj/Localizable.strings | 9 ++ .../Main/vi.lproj/Localizable.strings | 35 +++++--- .../Main/zh-Hans.lproj/Localizable.strings | 9 ++ FreeAPS/Sources/Models/Charts.swift | 5 ++ FreeAPS/Sources/Models/Configs.swift | 1 + .../Modules/Bolus/BolusStateModel.swift | 43 ++++++++++ .../View/AlternativeBolusCalcRootView.swift | 20 ++++- .../Modules/Bolus/View/BolusRootView.swift | 13 +++ .../Bolus/View/DefaultBolusCalcRootView.swift | 27 +++++- .../Sources/Modules/Home/HomeProvider.swift | 4 + .../Sources/Modules/Home/HomeStateModel.swift | 25 +++++- .../Modules/Home/View/HomeRootView.swift | 13 ++- .../Home/View/Previews/ActiveIOBView.swift | 85 +++++++++++++++++-- .../Settings/View/SettingsRootView.swift | 4 +- .../Sharing/View/SharingRootView.swift | 9 +- .../Services/Network/NightscoutManager.swift | 11 ++- 74 files changed, 996 insertions(+), 98 deletions(-) diff --git a/Config.xcconfig b/Config.xcconfig index c01f128018..0e9bee7e80 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -1,5 +1,5 @@ APP_DISPLAY_NAME = iAPS -APP_VERSION = 4.2.1 +APP_VERSION = 4.4.0 APP_BUILD_NUMBER = 1 COPYRIGHT_NOTICE = DEVELOPER_TEAM = ##TEAM_ID## diff --git a/Dependencies/CGMBLEKit/CGMBLEKit/ar.lproj/Localizable.strings b/Dependencies/CGMBLEKit/CGMBLEKit/ar.lproj/Localizable.strings index c1182284f6..603776c285 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKit/ar.lproj/Localizable.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKit/ar.lproj/Localizable.strings @@ -1,29 +1,29 @@ /* CGM display title */ -"Dexcom G5" = "Dexcom G5"; +"Dexcom G5" = "ديكسكوم G5"; /* CGM display title */ -"Dexcom G6" = "Dexcom G6"; +"Dexcom G6" = "ديكسكوم G6"; /* Error description for unreliable state */ "Glucose data is unavailable" = "Glucose data is unavailable"; /* Describes a low battery */ -"Low Battery" = "Low Battery"; +"Low Battery" = "البطارية منخفضة"; /* Describes a functioning transmitter */ "OK" = "موافق"; /* invlid config error description */ -"Peripheral command was invalid" = "Peripheral command was invalid"; +"Peripheral command was invalid" = "أمر الجهاز الطرفي غير صالح"; /* Timeout error description */ -"Peripheral did not respond in time" = "Peripheral did not respond in time"; +"Peripheral did not respond in time" = "الجهاز الطرفي لم يستجب في الوقت المحدد"; /* Not ready error description */ -"Peripheral isnʼt connected" = "Peripheral isnʼt connected"; +"Peripheral isnʼt connected" = "الجهاز الطرفي غير متصل"; /* The description of sensor calibration state when sensor calibration is ok. */ -"Sensor calibration is OK" = "Sensor calibration is OK"; +"Sensor calibration is OK" = "معايرة المستشعر مضبوطة"; /* The description of sensor calibration state when raw value is unknown. (1: missing data details) */ "Sensor is in unknown state %1$d" = "Sensor is in unknown state %1$d"; @@ -35,7 +35,7 @@ "Sensor is warming up" = "Sensor is warming up"; /* The description of sensor calibration state when sensor needs calibration. */ -"Sensor needs calibration" = "Sensor needs calibration"; +"Sensor needs calibration" = "المستشعر بحاجة الى معايرة"; /* Error description */ -"Unknown characteristic" = "Unknown characteristic"; +"Unknown characteristic" = "خاصية مجهولة"; diff --git a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings index a0e15f3cd7..19c2951fec 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings @@ -18,37 +18,37 @@ Title text for the button to remove a CGM from Loop */ "Glucose" = "قراءات السكر"; /* Describes a glucose value adjusted to reflect a recent calibration */ -"Glucose (Adjusted)" = "Glucose (Adjusted)"; +"Glucose (Adjusted)" = "جلوكوز (معدّلة)"; /* Section title for latest glucose calibration */ -"Latest Calibration" = "Latest Calibration"; +"Latest Calibration" = "أحدث معايرة"; /* Section title for latest glucose reading */ -"Latest Reading" = "Latest Reading"; +"Latest Reading" = "آخر قراءة"; /* Section title for latest connection date */ -"Latest Connection" = "Latest Connection"; +"Latest Connection" = "آخر اتصال"; /* Button title to open CGM app */ -"Open App" = "Open App"; +"Open App" = "فتح التطبيق"; /* Title describing sensor session age */ -"Session Age" = "Session Age"; +"Session Age" = "عمر الجلسة"; /* Section title for remote data synchronization */ -"Remote Data Synchronization" = "Remote Data Synchronization"; +"Remote Data Synchronization" = "مزامنة البيانات عن بعد"; /* Title describing sensor expiration */ -"Sensor Expires" = "Sensor Expires"; +"Sensor Expires" = "ينتهي المستشعر"; /* Title describing past sensor expiration */ -"Sensor Expired" = "Sensor Expired"; +"Sensor Expired" = "إنتهت صلاحية المستشعر"; /* Title describing CGM calibration and battery state */ "Status" = "الحالة"; /* Title describing transmitter session age */ -"Transmitter Age" = "Transmitter Age"; +"Transmitter Age" = "عمر جهاز الإرسال"; /* The title text for the Dexcom G5/G6 transmitter ID config value */ "Transmitter ID" = "Transmitter ID"; diff --git a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings index e019c4c516..b3b8878728 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings @@ -1,20 +1,20 @@ /* Class = "UILabel"; text = "Credentials"; ObjectID = "5oU-vK-JHQ"; */ -"5oU-vK-JHQ.text" = "Credentials"; +"5oU-vK-JHQ.text" = "بيانات التسجيل"; /* Class = "UITableViewController"; title = "Transmitter Setup"; ObjectID = "Dds-49-o7G"; */ -"Dds-49-o7G.title" = "Transmitter Setup"; +"Dds-49-o7G.title" = "إعداد جهاز الإرسال"; /* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */ "GOT-KQ-cEh.text" = "تفاصيل"; /* 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" = "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."; +"Qub-6B-0aB.footerTitle" = "يمكن العثور على معرف جهاز الإرسال مطبوعا على الجزء الخلفي من الجهاز، على جانب الصندوق الذي أتى به، ومن داخل قوائم الإعدادات الخاصة بالمستلم والتطبيق الموبايل."; /* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */ "Qub-6B-0aB.headerTitle" = "Transmitter ID"; /* 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" = "Data can be downloaded over the Internet from Share when the transmitter connection fails."; +"k1N-Rg-XDy.footerTitle" = "يمكن تحميل البيانات عبر الإنترنت من تطبيق Share عند فشل اتصال المرسل."; /* Class = "UITableViewSection"; headerTitle = "Dexcom Share"; ObjectID = "k1N-Rg-XDy"; */ "k1N-Rg-XDy.headerTitle" = "Dexcom Share"; diff --git a/Dependencies/MinimedKit/MinimedKit/Resources/tr.lproj/Localizable.strings b/Dependencies/MinimedKit/MinimedKit/Resources/tr.lproj/Localizable.strings index d3050249d3..a5da24edc0 100644 --- a/Dependencies/MinimedKit/MinimedKit/Resources/tr.lproj/Localizable.strings +++ b/Dependencies/MinimedKit/MinimedKit/Resources/tr.lproj/Localizable.strings @@ -50,10 +50,10 @@ "Minimed %@" = "Minimed %@"; /* Generic title of the minimed pump manager */ -"Minimed 500/700 Series" = "Minimed 500/700 Series"; +"Minimed 500/700 Series" = "Minimed 500/700 Serisi"; /* Describing the North America pump region */ -"North America" = "North America"; +"North America" = "Kuzey Amerika"; /* No comment provided by engineer. */ "Pump did not respond" = "Pompa yanıt vermedi"; @@ -95,4 +95,4 @@ "Unknown response during %1$@: %2$@" = "%1$@: %2$@ sırasında bilinmeyen yanıt"; /* Describing the worldwide pump region */ -"World-Wide" = "World-Wide"; +"World-Wide" = "Dünya Çapında"; diff --git a/Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings index fe679fda31..d578a852ca 100644 --- a/Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspend Insulin Delivery"; diff --git a/Dependencies/OmniBLE/Localizations/da.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/da.lproj/Localizable.strings index f3391d85c1..3228c1dda2 100644 --- a/Dependencies/OmniBLE/Localizations/da.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/da.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Tillidspåmindelser"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Påmindelse om succesfulde aktiviteter er bip fra Pod'en, som kan bruges til at bekræfte valgte kommandoer. når Pod'en ikke er bragt til tavshed."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspenderer Insulinlevering"; diff --git a/Dependencies/OmniBLE/Localizations/de.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/de.lproj/Localizable.strings index 749c3bec7b..b0cd9a3200 100644 --- a/Dependencies/OmniBLE/Localizations/de.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/de.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Pumpe Bestätigungstöne"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Vertrauenserinnerungen sind Pieptöne vom Pod, die verwendet werden können, um ausgewählte Befehle zu bestätigen."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Erinnerungen"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "Die App konfiguriert eine Erinnerung auf dem Pod, um Dich vor Ablauf des Pods zu benachrichtigen. Legen die Anzahl der Stunden fest, die Du vor Ablauf des Pods benachrichtigt werden möchtest."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "Die Ablauf-Erinnerung für den aktuellen Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "Die App benachrichtigt dich, wenn die Insulinmenge im Pod diesen Wert unterschreitet."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "Die obigen Erinnerungen ertönen nicht, wenn sich Dein Gerät im Lautlos- oder Nicht-Stören-Modus befindet.\n\nEs gibt andere kritische Pod-Warnungen und -Alarme, die auch dann ertönen, wenn Dein Gerät auf „Lautlos“ oder „Nicht stören“ eingestellt ist."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Insulinabgabe unterbrechen"; diff --git a/Dependencies/OmniBLE/Localizations/en.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/en.lproj/Localizable.strings index 0c676e4cd0..75d3ead3d9 100644 --- a/Dependencies/OmniBLE/Localizations/en.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/en.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspend Insulin Delivery"; diff --git a/Dependencies/OmniBLE/Localizations/es.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/es.lproj/Localizable.strings index 8a034234bf..fb519dffc0 100644 --- a/Dependencies/OmniBLE/Localizations/es.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/es.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Recordatorios de confianza"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Los recordatorios de confianza son pitidos que emite el Pod que pueden utilizarse para tener certeza de que se han ejecutado comandos cuando no está silenciado."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspender administración insulina"; diff --git a/Dependencies/OmniBLE/Localizations/fi.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/fi.lproj/Localizable.strings index a3faaa34dc..0c5ee7e8f1 100644 --- a/Dependencies/OmniBLE/Localizations/fi.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/fi.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspend Insulin Delivery"; diff --git a/Dependencies/OmniBLE/Localizations/fr.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/fr.lproj/Localizable.strings index 6b38e20e39..25e14853fe 100644 --- a/Dependencies/OmniBLE/Localizations/fr.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/fr.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Rappels de confiance"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Les rappels de confiance sont des bips émis par le Pod qui peuvent être utilisés pour confirmer l'exécution des commandes sélectionnées."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Rappels du Pod"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "L'application configure un rappel enregistré sur le Pod pour vous avertir à l'avance de son expiration. Au moment de l'appairage d'un nouveau Pod, définissez le nombre d'heures de préavis que vous souhaitez."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "Le temps de rappel d'expiration pour le Pod actuel."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "L'application vous avertit quand la quantité d'insuline restante dans le Pod atteint cette limite."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "Les rappels ci-dessus ne sonneront pas dans l'application si votre appareil est en mode Silencieux ou Ne pas déranger. Il existent d'autres alertes critiques de Pod qui sonneront dans l'application même si votre appareil est en mode Silent ou Ne pas déranger.\n\nLe Pod lui-même utilisera également des bips audibles pour tous les rappels et alertes de Pod, sauf si le Pod est réglé sur Silencieux."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspendre l'administration d'insuline"; diff --git a/Dependencies/OmniBLE/Localizations/he.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/he.lproj/Localizable.strings index fe679fda31..d578a852ca 100644 --- a/Dependencies/OmniBLE/Localizations/he.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/he.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspend Insulin Delivery"; diff --git a/Dependencies/OmniBLE/Localizations/hu.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/hu.lproj/Localizable.strings index 67001aa7fa..6d5b03cb90 100644 --- a/Dependencies/OmniBLE/Localizations/hu.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/hu.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspend Insulin Delivery"; diff --git a/Dependencies/OmniBLE/Localizations/it.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/it.lproj/Localizable.strings index 146712406f..aa63b2df1e 100644 --- a/Dependencies/OmniBLE/Localizations/it.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/it.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Promemoria Di Sicurezza"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "I promemoria di fiducia sono segnali acustici emessi dal Pod che possono essere utilizzati per confermare i comandi selezionati quando il Pod non è silenziato."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Promemoria Pod"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "L'app configura un promemoria sul Pod per notificarti in anticipo della scadenza del Pod. Puoi impostare il numero di ore di preavviso che desideri configurare quando abbini un nuovo Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "Il promemoria di scadenza per l'attuale pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "L'App ti avvisa quando la quantità d'insulina nel Pod raggiunge questo livello."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "I promemoria qui sopra non suoneranno se il dispositivo è in modalità silenzioso o non disturbare. Ci sono altri avvisi e avvisi di pod critici che suoneranno anche se il dispositivo è impostato in modalità silenzioso o non disturbare. \n\n Il pod emetterà avvisi sonori per tutti gli avvisi del pod tranne quando il pod stesso è silenziato."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Sospendi diffusione di Insulina"; diff --git a/Dependencies/OmniBLE/Localizations/nb.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/nb.lproj/Localizable.strings index 73c7d76f29..4a5da9339e 100644 --- a/Dependencies/OmniBLE/Localizations/nb.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/nb.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Bekreftelseslyder"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Påminnelsesvarsler er pipelyder fra Pod'en som kan brukes til å bekrefte valgte kommandoer når Pod'en ikke er i stillemodus."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Sett insulintilførsel på pause"; diff --git a/Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings index 9abb4d8aad..dcf0fa7df6 100644 --- a/Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Bevestigingsmeldingen met piepjes vanuit de Pod"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Dit zijn meldingen die met piepjes uit de Pod komen en kunnen worden gebruikt ter bevestiging van geselecteerde opdrachten."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod herinneringen"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "iAPS configureert melding op de Pod om je op de hoogte te stellen wanneer de Pod verloopt. Stel het aantal uren vooraf in dat je standaard wilt instellen als je een nieuwe Pod koppelt."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "De verloopherinneringstijd voor de huidige Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "iAPS geeft een melding als de hoeveelheid insuline in de Pod dit niveau bereikt."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "Als je apparaat stil is of in de Niet storen-modus staat, hoor je de bovenstaande herinneringen niet in de app. Er zijn echter andere belangrijke waarschuwingen voor de Pod die nog steeds in de app verschijnen, zelfs als je apparaat stil is of in de Niet storen-modus staat.\n\nDe Pod maakt ook geluid met piepjes voor alle herinneringen en waarschuwingen, behalve als de Pod is uitgeschakeld."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Onderbreken van insulinetoediening"; @@ -721,7 +739,7 @@ "The App notifies you when the amount of insulin in the Pod reaches this level (50-10 U).\n\nScroll to set the number of units at which you would like to be reminded." = "iAPS geeft een melding als de hoeveelheid insuline in de Pod dit niveau bereikt (50-10 E).\n\nScroll om in te stellen bij welk aantal eenheden je wilt worden herinnerd."; /* Label text for low reservoir value row */ -"Low Reservoir" = "Reservoir bijna leeg"; +"Low Reservoir" = "Laag reservoir niveau"; /* */ "Save" = "Opslaan"; diff --git a/Dependencies/OmniBLE/Localizations/pl.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/pl.lproj/Localizable.strings index 954ba1430c..9c6584c9a7 100644 --- a/Dependencies/OmniBLE/Localizations/pl.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/pl.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspend Insulin Delivery"; diff --git a/Dependencies/OmniBLE/Localizations/pt-BR.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/pt-BR.lproj/Localizable.strings index 0fe28b9a3a..097e556f40 100644 --- a/Dependencies/OmniBLE/Localizations/pt-BR.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/pt-BR.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspend Insulin Delivery"; diff --git a/Dependencies/OmniBLE/Localizations/pt-PT.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/pt-PT.lproj/Localizable.strings index 69d0d541c4..38d979a498 100644 --- a/Dependencies/OmniBLE/Localizations/pt-PT.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/pt-PT.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Suspend Insulin Delivery"; diff --git a/Dependencies/OmniBLE/Localizations/ru.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/ru.lproj/Localizable.strings index d4f6d8eb98..ede4806856 100644 --- a/Dependencies/OmniBLE/Localizations/ru.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/ru.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Напоминания о верификации"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Доверенные звуковые сигналы от Пода, которые позволяют распознать выбранные команды, когда Под не заглушен."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Напоминания пода"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "Приложение настраивает напоминание на телефоне, чтобы уведомить вас об истечении срока действия Pod. Установите количество часов предварительного уведомления, которое вы хотите сохранить при сопряжении нового Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "Напоминание об истечении срока действия текущего Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "Приложение уведомит вас, когда количество инсулина в Pod достигнет этого уровня."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "Вышеуказанные напоминания не будут звучать в приложении, если ваше устройство находится в режиме \"Без звука\" или \"Не беспокоить\". Существуют другие важные оповещения Pod, которые будут звучать в приложении, даже если на вашем устройстве установлен режим \"Без звука\" или \"Не беспокоить\".\n\nPod также будет использовать звуковые сигналы для всех напоминаний и оповещений Pod, за исключением тех случаев, когда Pod заглушен."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Приостановить подачу инсулина"; diff --git a/Dependencies/OmniBLE/Localizations/sk.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/sk.lproj/Localizable.strings index dd09523948..da7d0d389b 100644 --- a/Dependencies/OmniBLE/Localizations/sk.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/sk.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Upozornenia s pípnutím zo zariadenia Pod"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Upozornenia s pípnutím zo zariadenia Pod, ktoré možno použiť na potvrdenie vybraných príkazov, keď zariadenie Pod nie je vypnuté."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Zastavte podávanie inzulínu"; diff --git a/Dependencies/OmniBLE/Localizations/sv.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/sv.lproj/Localizable.strings index 06ee9494f4..20484f633a 100644 --- a/Dependencies/OmniBLE/Localizations/sv.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/sv.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Bekräftelseljud"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Bekräftelseljud är pip från podden (när den inte är tystad) som kan användas som bekräftelser på utförda kommandon."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Påminnelser"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "Du kan ange när du vill att appen ska påminna dig om när du behöver byta podd. Ange det antal timmar i förväg du vil ha påminnelsen."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "Påminnelsetid om utgångsdatum för din aktuella podd."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "Appen kommer att visa notis när mängden insulin når denna nivå."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "Påminnelser kommer vara tysta när 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 eller stör ej - läge, förutom om podden är tystad."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Pausa insulintillförsel"; diff --git a/Dependencies/OmniBLE/Localizations/tr.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/tr.lproj/Localizable.strings index c27b0ae766..5ef0175c48 100644 --- a/Dependencies/OmniBLE/Localizations/tr.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/tr.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Onay sesi"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "İnsülin İletimini Askıya Al"; diff --git a/Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings index 7abb507458..7a1af2719b 100644 --- a/Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Нагадування про Верифікацію"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Сигнали підтвердження — це звукові сигнали Podʼа, які можна використовувати для підтвердження вибраних команд."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Нагадування Pod"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "Програма налаштовує нагадування на телефоні, щоб повідомити вас про закінчення терміну дії Pod. Встановіть кількість годин попереднього повідомлення, яку потрібно зберегти при поєднанні нового Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "Нагадування про закінчення дії поточного Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "Програма повідомить вас, коли кількість інсуліну в Pod досягне цього рівня."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "Вказані вище нагадування не звучатимуть у програмі, якщо пристрій знаходиться в режимі \"Без звуку\" або \"Не турбувати\". Існують інші важливі оповіщення Pod, які будуть звучати в програмі, навіть якщо на пристрої встановлено режим \"Без звуку\" або \"Не турбувати\", коли Pod заглушено."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Призупинити Введення Інсуліну"; @@ -585,7 +603,7 @@ /* Description text for critical alerts */ "The reminders above will not sound if your device is in Silent or Do Not Disturb mode.\n\nThere are other critical Pod alerts and alarms that will sound even if your device is set to Silent or Do Not Disturb mode." = "Нагадування вище не звучатимуть, якщо ваш пристрій перебуває в беззвучному режимі або режимі «Не турбувати».\n\nІснують інші важливі сповіщення та будильники Podʼу, які лунатимуть, навіть якщо на пристрої встановлено режим «Без звуку» або «Не турбувати»."; /* navigation title for notification settings */ -"Notification Settings" = "Параметри Сповіщень"; +"Notification Settings" = "Параметри сповіщень"; /* Label for scheduled reminder value row */ "Time" = "Час"; diff --git a/Dependencies/OmniBLE/Localizations/vi.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/vi.lproj/Localizable.strings index 6012dd3ae1..eee91e908d 100644 --- a/Dependencies/OmniBLE/Localizations/vi.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/vi.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "Confidence Reminders"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Lời nhắc là những tiếng bíp từ Pod có thể được sử dụng để xác nhận các lệnh đã chọn khi Pod không ở chế độ im lặng."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Lời nhắc Pod"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "Ứng dụng cấu hình lời nhắc trên pod để thông báo trước cho bạn khi pod hết hạn. Đặt số giờ bạn muốn cấu hình khi ghép nối pod mới."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "Thời gian nhắc nhở hết hạn cho Pod hiện tại."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "Ứng dụng nhắc nhở bạn khi lượng insulin trong pod đạt đến mức này."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "Lời nhắc ở trên sẽ không phát ra âm thanh nếu thiết bị của bạn ở trạng thái Silent hoặc Do Not Disturb.\n\n Có nhiều cách cảnh báo khác nhau phát ra âm thanh ngay cả khi thiết bị của bạn ở trạng thái im lặng hoặc không làm phiền."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "Tạm dừng insulin"; @@ -721,7 +739,7 @@ "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." = "Ứng dụng sẽ thông báo khi lượng insulin trong Pod đạt đến mức này (50-10 U).\n\n Kéo xuống để chọn số Unit mà bạn muốn để nhận thông báo."; /* Label text for low reservoir value row */ -"Low Reservoir" = "Sắp hết thuốc"; +"Low Reservoir" = "Sắp hết insulin"; /* */ "Save" = "Lưu"; diff --git a/Dependencies/OmniBLE/Localizations/zh-Hans.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/zh-Hans.lproj/Localizable.strings index 290ba679f1..d36ea3a860 100644 --- a/Dependencies/OmniBLE/Localizations/zh-Hans.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/zh-Hans.lproj/Localizable.strings @@ -80,6 +80,24 @@ /* */ "Confidence Reminders" = "二次确认提醒"; +/* Help text for BeepPreferenceSelectionView */ +"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced."; + +/* Title for pod reminders section */ +"Pod Reminders" = "Pod Reminders"; + +/* Footer text for pod 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 by default when pairing a new Pod." = "The app configures a reminder on the Pod to notify you in advance of Pod expiration. Set the number of hours advance notice you would like to configure by default when pairing a new Pod."; + +/* Footer text for scheduled reminder area */ +"The expiration reminder time for the current Pod." = "The expiration reminder time for the current Pod."; + +/* Footer text for low reservoir value row */ +"The app notifies you when the amount of insulin in the Pod reaches this level." = "The app notifies you when the amount of insulin in the Pod reaches this level."; + +/* Description text for critical alerts */ +"The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced." = "The above reminders will not sound in the app if your device is in Silent or Do Not Disturb mode. There are other critical Pod alerts that will sound in the app even if your device is set to Silent or Do Not Disturb mode.\n\nThe Pod will also use audible beeps for all Pod reminders and alerts except when the Pod is Silenced."; + /* Text for suspend resume button when insulin delivery active */ "Suspend Insulin Delivery" = "暂停胰岛素输注"; @@ -721,7 +739,7 @@ "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."; /* Label text for low reservoir value row */ -"Low Reservoir" = "低药量"; +"Low Reservoir" = "Low Reservoir"; /* */ "Save" = "保存​​"; diff --git a/Dependencies/OmniBLE/OmniBLE/OmnipodCommon/Pod.swift b/Dependencies/OmniBLE/OmniBLE/OmnipodCommon/Pod.swift index 5bd1d6b5fd..f094b2747e 100644 --- a/Dependencies/OmniBLE/OmniBLE/OmnipodCommon/Pod.swift +++ b/Dependencies/OmniBLE/OmniBLE/OmnipodCommon/Pod.swift @@ -97,22 +97,25 @@ public struct Pod { } // DeliveryStatus used in StatusResponse and DetailedStatus +// Since bits 1 & 2 are exclusive and bits 4 & 8 are exclusive, +// these are all the possible values that can be returned. public enum DeliveryStatus: UInt8, CustomStringConvertible { case suspended = 0 case scheduledBasal = 1 case tempBasalRunning = 2 - case priming = 4 + case priming = 4 // bolusing while suspended, should only occur during priming case bolusInProgress = 5 case bolusAndTempBasal = 6 + case extendedBolusWhileSuspended = 8 // should never occur case extendedBolusRunning = 9 case extendedBolusAndTempBasal = 10 public var suspended: Bool { - return self == .suspended + return self == .suspended || self == .priming || self == .extendedBolusWhileSuspended } public var bolusing: Bool { - return self == .bolusInProgress || self == .bolusAndTempBasal || self == .extendedBolusRunning || self == .extendedBolusAndTempBasal + return self == .bolusInProgress || self == .bolusAndTempBasal || self == .extendedBolusRunning || self == .extendedBolusAndTempBasal || self == .priming || self == .extendedBolusWhileSuspended } public var tempBasalRunning: Bool { @@ -120,7 +123,7 @@ public enum DeliveryStatus: UInt8, CustomStringConvertible { } public var extendedBolusRunning: Bool { - return self == .extendedBolusRunning || self == .extendedBolusAndTempBasal + return self == .extendedBolusRunning || self == .extendedBolusAndTempBasal || self == .extendedBolusWhileSuspended } public var description: String { @@ -137,6 +140,8 @@ public enum DeliveryStatus: UInt8, CustomStringConvertible { return LocalizedString("Bolusing", comment: "Delivery status when bolusing") case .bolusAndTempBasal: return LocalizedString("Bolusing with temp basal", comment: "Delivery status when bolusing and temp basal is running") + case .extendedBolusWhileSuspended: + return LocalizedString("Extended bolus running while suspended", comment: "Delivery status when extended bolus is running while suspended") case .extendedBolusRunning: return LocalizedString("Extended bolus running", comment: "Delivery status when extended bolus is running") case .extendedBolusAndTempBasal: diff --git a/Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 5feaca612f..2ca87e4b11 100644 --- a/Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -1841,7 +1841,7 @@ extension OmniBLEPumpManager: PumpManager { state.bolusEngageState = .engaging }) - if case .some(.suspended) = self.state.podState?.suspendState { + if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended != false { self.log.error("enactBolus: returning pod suspended error for bolus") completion(.deviceState(PodCommsError.podSuspended)) return @@ -1982,7 +1982,7 @@ extension OmniBLEPumpManager: PumpManager { return } - if case (.suspended) = podState.suspendState { + if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended != false { self.log.info("Not enacting temp basal because podState indicates pod is suspended.") completion(.deviceState(PodCommsError.podSuspended)) return @@ -2515,7 +2515,9 @@ extension OmniBLEPumpManager { if alert.alertIdentifier == alertIdentifier || alert.repeatingAlertIdentifier == alertIdentifier { // If this alert was triggered by the pod find the slot to clear it. if let slot = alert.triggeringSlot { - if case .some(.suspended) = self.state.podState?.suspendState, slot == .slot6SuspendTimeExpired { + if (self.state.podState?.isSuspended == true || self.state.podState?.lastDeliveryStatusReceived?.suspended == true) && + slot == .slot6SuspendTimeExpired + { // Don't clear this pod alert here with the pod still suspended so that the suspend time expired // pod alert beeping will continue until the pod is resumed which will then deactivate this alert. log.default("Skipping acknowledgement of suspend time expired alert with a suspended pod") diff --git a/Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift b/Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift index 5d223ee2fc..c31ae0dd28 100644 --- a/Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift +++ b/Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift @@ -802,7 +802,9 @@ public class PodCommsSession { let _: StatusResponse = try send([CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: .all, beepType: .noBeepCancel)]) } } + podState.unacknowledgedCommand = PendingCommand.program(.basalProgram(schedule: schedule), transport.messageNumber, currentDate) var status: StatusResponse = try send([basalScheduleCommand, basalExtraCommand]) + podState.unacknowledgedCommand = nil let now = currentDate podState.suspendState = .resumed(now) podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: now, scheduledCertainty: .certain, insulinType: podState.insulinType) @@ -813,12 +815,12 @@ public class PodCommsSession { } podState.updateFromStatusResponse(status, at: currentDate) return status - } catch PodCommsError.nonceResyncFailed { - throw PodCommsError.nonceResyncFailed - } catch PodCommsError.rejectedMessage(let errorCode) { - throw PodCommsError.rejectedMessage(errorCode: errorCode) + } catch PodCommsError.unacknowledgedMessage(let seq, let error) { + podState.unacknowledgedCommand = podState.unacknowledgedCommand?.commsFinished + log.error("Unacknowledged resume: command seq = %d, error = %{public}@", seq, String(describing: error)) + throw error } catch let error { - podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: currentDate, scheduledCertainty: .uncertain, insulinType: podState.insulinType) + podState.unacknowledgedCommand = nil throw error } } diff --git a/Dependencies/OmniKit/OmniKit/OmnipodCommon/Pod.swift b/Dependencies/OmniKit/OmniKit/OmnipodCommon/Pod.swift index 4e6308c145..2acaf2cd54 100644 --- a/Dependencies/OmniKit/OmniKit/OmnipodCommon/Pod.swift +++ b/Dependencies/OmniKit/OmniKit/OmnipodCommon/Pod.swift @@ -99,22 +99,25 @@ public struct Pod { } // DeliveryStatus used in StatusResponse and DetailedStatus +// Since bits 1 & 2 are exclusive and bits 4 & 8 are exclusive, +// these are all the possible values that can be returned. public enum DeliveryStatus: UInt8, CustomStringConvertible { case suspended = 0 case scheduledBasal = 1 case tempBasalRunning = 2 - case priming = 4 + case priming = 4 // bolusing while suspended, should only occur during priming case bolusInProgress = 5 case bolusAndTempBasal = 6 + case extendedBolusWhileSuspended = 8 // should never occur case extendedBolusRunning = 9 case extendedBolusAndTempBasal = 10 public var suspended: Bool { - return self == .suspended + return self == .suspended || self == .priming || self == .extendedBolusWhileSuspended } public var bolusing: Bool { - return self == .bolusInProgress || self == .bolusAndTempBasal || self == .extendedBolusRunning || self == .extendedBolusAndTempBasal + return self == .bolusInProgress || self == .bolusAndTempBasal || self == .extendedBolusRunning || self == .extendedBolusAndTempBasal || self == .priming || self == .extendedBolusWhileSuspended } public var tempBasalRunning: Bool { @@ -122,7 +125,7 @@ public enum DeliveryStatus: UInt8, CustomStringConvertible { } public var extendedBolusRunning: Bool { - return self == .extendedBolusRunning || self == .extendedBolusAndTempBasal + return self == .extendedBolusRunning || self == .extendedBolusAndTempBasal || self == .extendedBolusWhileSuspended } public var description: String { @@ -139,6 +142,8 @@ public enum DeliveryStatus: UInt8, CustomStringConvertible { return LocalizedString("Bolusing", comment: "Delivery status when bolusing") case .bolusAndTempBasal: return LocalizedString("Bolusing with temp basal", comment: "Delivery status when bolusing and temp basal is running") + case .extendedBolusWhileSuspended: + return LocalizedString("Extended bolus running while suspended", comment: "Delivery status when extended bolus is running while suspended") case .extendedBolusRunning: return LocalizedString("Extended bolus running", comment: "Delivery status when extended bolus is running") case .extendedBolusAndTempBasal: diff --git a/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift b/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift index 052bd0c80b..0888bcdd5b 100644 --- a/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -1856,7 +1856,7 @@ extension OmnipodPumpManager: PumpManager { state.bolusEngageState = .engaging }) - if case .some(.suspended) = self.state.podState?.suspendState { + if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended != false { self.log.error("enactBolus: returning pod suspended error for bolus") completion(.deviceState(PodCommsError.podSuspended)) return @@ -1995,7 +1995,7 @@ extension OmnipodPumpManager: PumpManager { return } - if case (.suspended) = podState.suspendState { + if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended != false { self.log.info("Not enacting temp basal because podState indicates pod is suspended.") completion(.deviceState(PodCommsError.podSuspended)) return @@ -2504,7 +2504,9 @@ extension OmnipodPumpManager { if alert.alertIdentifier == alertIdentifier || alert.repeatingAlertIdentifier == alertIdentifier { // If this alert was triggered by the pod find the slot to clear it. if let slot = alert.triggeringSlot { - if case .some(.suspended) = self.state.podState?.suspendState, slot == .slot6SuspendTimeExpired { + if (self.state.podState?.isSuspended == true || self.state.podState?.lastDeliveryStatusReceived?.suspended == true) && + slot == .slot6SuspendTimeExpired + { // Don't clear this pod alert here with the pod still suspended so that the suspend time expired // pod alert beeping will continue until the pod is resumed which will then deactivate this alert. log.default("Skipping acknowledgement of suspend time expired alert with a suspended pod") diff --git a/Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift b/Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift index 086834fcbf..9c8298dcde 100644 --- a/Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift +++ b/Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift @@ -777,7 +777,9 @@ public class PodCommsSession { let _: StatusResponse = try send([CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: .all, beepType: .noBeepCancel)]) } } + podState.unacknowledgedCommand = PendingCommand.program(.basalProgram(schedule: schedule), transport.messageNumber, currentDate) var status: StatusResponse = try send([basalScheduleCommand, basalExtraCommand]) + podState.unacknowledgedCommand = nil let now = currentDate podState.suspendState = .resumed(now) podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: now, scheduledCertainty: .certain, insulinType: podState.insulinType) @@ -788,12 +790,12 @@ public class PodCommsSession { } podState.updateFromStatusResponse(status, at: currentDate) return status - } catch PodCommsError.nonceResyncFailed { - throw PodCommsError.nonceResyncFailed - } catch PodCommsError.rejectedMessage(let errorCode) { - throw PodCommsError.rejectedMessage(errorCode: errorCode) + } catch PodCommsError.unacknowledgedMessage(let seq, let error) { + podState.unacknowledgedCommand = podState.unacknowledgedCommand?.commsFinished + log.error("Unacknowledged resume: command seq = %d, error = %{public}@", seq, String(describing: error)) + throw error } catch let error { - podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: currentDate, scheduledCertainty: .uncertain, insulinType: podState.insulinType) + podState.unacknowledgedCommand = nil throw error } } diff --git a/FreeAPS/Resources/javascript/bundle/glucose-get-last.js b/FreeAPS/Resources/javascript/bundle/glucose-get-last.js index e4ab63b78c..6af4739a44 100644 --- a/FreeAPS/Resources/javascript/bundle/glucose-get-last.js +++ b/FreeAPS/Resources/javascript/bundle/glucose-get-last.js @@ -1 +1 @@ -var freeaps_glucoseGetLast;(()=>{var e={6237:e=>{function t(e){return e.date||Date.parse(e.display_time)||Date.parse(e.dateString)}e.exports=function(e){for(var r=(e=e.filter((function(e){return e.glucose||e.sgv})).map((function(e){if(e.glucose=e.glucose||e.sgv,null!==e.glucose)return e})))[0],o=t(r),n=[],u=[],a=[],l=0,s=1;s38&&e[s].device===r.device){var c,d=e[s],g=t(d),i=0;void 0!==g&&void 0!==o?(c=Math.round((o-g)/6e4),i=(r.glucose-d.glucose)/c*5):console.error("Error: date field not found: cannot calculate avgdelta"),-20&&(v=n.reduce((function(e,t){return e+t}))/n.length),u.length>0&&(f=u.reduce((function(e,t){return e+t}))/u.length),a.length>0&&(h=a.reduce((function(e,t){return e+t}))/a.length),{delta:Math.round(100*v)/100,glucose:Math.round(100*r.glucose)/100,noise:Math.round(r.noise),short_avgdelta:Math.round(100*f)/100,long_avgdelta:Math.round(100*h)/100,date:o,last_cal:l,device:r.device}}}},t={};var r=function r(o){var n=t[o];if(void 0!==n)return n.exports;var u=t[o]={exports:{}};return e[o](u,u.exports,r),u.exports}(6237);freeaps_glucoseGetLast=r})(); \ No newline at end of file +var freeaps_glucoseGetLast;(()=>{var e={6237:e=>{function t(e){return e.date||Date.parse(e.display_time)||Date.parse(e.dateString)}e.exports=function(e){for(var r=void 0,o=void 0,a=[],n=[],u=[],s=0,l=0;l38&&i.device===r.device){var c,d=i,g=t(d),v=0;if(void 0===g||void 0===o){console.error("Error: date field not found: cannot calculate avgdelta");continue}if(c=Math.round((o-g)/6e4),v=(r.glucose-d.glucose)/c*5,-242.5)break}}}else r=i,o=t(i)}var f=0,h=0,p=0;return a.length>0&&(f=a.reduce((function(e,t){return e+t}))/a.length),n.length>0&&(h=n.reduce((function(e,t){return e+t}))/n.length),u.length>0&&(p=u.reduce((function(e,t){return e+t}))/u.length),{delta:Math.round(100*f)/100,glucose:Math.round(100*r.glucose)/100,noise:Math.round(r.noise),short_avgdelta:Math.round(100*h)/100,long_avgdelta:Math.round(100*p)/100,date:o,last_cal:s,device:r.device}}}},t={};var r=function r(o){var a=t[o];if(void 0!==a)return a.exports;var n=t[o]={exports:{}};return e[o](n,n.exports,r),n.exports}(6237);freeaps_glucoseGetLast=r})(); \ No newline at end of file diff --git a/FreeAPS/Resources/javascript/prepare/determine-basal.js b/FreeAPS/Resources/javascript/prepare/determine-basal.js index f0ada2bde2..66722ba0c4 100644 --- a/FreeAPS/Resources/javascript/prepare/determine-basal.js +++ b/FreeAPS/Resources/javascript/prepare/determine-basal.js @@ -158,7 +158,7 @@ function dynisf(profile, autosens_data, dynamicVariables, glucose) { } // Account for TDD of insulin. Compare last 2 hours with total data (up to 10 days) - const tdd_factor = weighted_average / average14; // weighted average TDD / total data average TDD + var tdd_factor = weighted_average / average14; // weighted average TDD / total data average TDD const enable_sigmoid = profile.sigmoid; var newRatio = 1; @@ -202,7 +202,7 @@ function dynisf(profile, autosens_data, dynamicVariables, glucose) { console.log(", Dynamic ISF limited by autosens_max setting to: " + autosens_max + ", from: " + newRatio); newRatio = autosens_max; } else if (newRatio < autosens_min) { - console.log("Dynamic ISF limited by autosens_min setting to: " + autosens_min + ", from: " + newRatio); + console.log(", Dynamic ISF limited by autosens_min setting to: " + autosens_min + ", from: " + newRatio); newRatio = autosens_min; } @@ -224,7 +224,7 @@ function dynisf(profile, autosens_data, dynamicVariables, glucose) { // Basal Adjustment if (profile.tddAdjBasal && dynISFenabled) { profile.current_basal *= tdd_factor; - console.log("Dynamic ISF. Basal adjusted with TDD factor: " + round(tdd_factor, 1)); + console.log("Dynamic ISF. Basal adjusted with TDD factor: " + round(tdd_factor, 2)); } } diff --git a/FreeAPS/Resources/javascript/prepare/middleware.js b/FreeAPS/Resources/javascript/prepare/middleware.js index ddc401b60e..20c5da89a3 100644 --- a/FreeAPS/Resources/javascript/prepare/middleware.js +++ b/FreeAPS/Resources/javascript/prepare/middleware.js @@ -11,9 +11,16 @@ function generate(iob, currenttemp, glucose, profile, autosens = null, meal = nu }; if (profile.tddAdjBasal && dynamicVariables.average_total_data != 0) { - profile.tdd_factor = Math.round((dynamicVariables.weightedAverage / dynamicVariables.average_total_data) * 100) / 100; + profile.tdd_factor = Math.round( (dynamicVariables.weightedAverage / dynamicVariables.average_total_data) * 100) / 100; + + const adjusted = Math.min(Math.max(profile.autosens_min, profile.tdd_factor), profile.autosens_max); + + if (profile.tdd_factor != adjusted) { + console.log("Dynamic basal adjustment limited by your autosens_min/max settings to: " + adjusted); + profile.tdd_factor = adjusted; + } } - + if (profile.useNewFormula && profile.temptargetSet && (profile.high_temptarget_raises_sensitivity || profile.exercise_mode || dynamicVariables.isEnabled) && profile.min_bg >= 118) { profile.useNewFormula = false; console.log("Dynamic ISF disabled due to an active exercise "); diff --git a/FreeAPS/Sources/APS/APSManager.swift b/FreeAPS/Sources/APS/APSManager.swift index 8b14b0c738..c8f7c09ff7 100644 --- a/FreeAPS/Sources/APS/APSManager.swift +++ b/FreeAPS/Sources/APS/APSManager.swift @@ -43,6 +43,9 @@ enum APSError: LocalizedError { case apsError(message: String) case deviceSyncError(message: String) case manualBasalTemp(message: String) + case activeBolusViewBolus + case activeBolusViewBasal + case activeBolusViewBasalandBolus var errorDescription: String? { switch self { @@ -60,6 +63,12 @@ enum APSError: LocalizedError { return "Sync error: \(message)" case let .manualBasalTemp(message): return "Manual Basal Temp : \(message)" + case .activeBolusViewBolus: + return "Suggested SMB not enacted while in Bolus View" + case .activeBolusViewBasal: + return "Suggested Temp Basal (when > 0) not enacted while in Bolus View" + case .activeBolusViewBasalandBolus: + return "Suggested Temp Basal (when > 0) and SMB not enacted while in Bolus View" } } } @@ -552,6 +561,13 @@ final class BaseAPSManager: APSManager, Injectable { processError(error) return } + + guard !activeBolusView() else { + debug(.apsManager, "Not enacting while in Bolus View") + processError(APSError.activeBolusViewBolus) + return + } + let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount)) pump.enactBolus(units: roundedAmount, activationType: .manualRecommendationAccepted) { error in if let error = error { @@ -614,6 +630,13 @@ final class BaseAPSManager: APSManager, Injectable { processError(error) return } + + guard !activeBolusView() || (activeBolusView() && rate == 0) else { + debug(.apsManager, "Not enacting while in Bolus View") + processError(APSError.activeBolusViewBasal) + return + } + // unable to do temp basal during manual temp basal 😁 if isManualTempBasal { processError(APSError.manualBasalTemp(message: "Loop not possible during the manual basal temp")) @@ -753,6 +776,14 @@ final class BaseAPSManager: APSManager, Injectable { return Just(()).setFailureType(to: Error.self) .eraseToAnyPublisher() } + + guard !self.activeBolusView() || (self.activeBolusView() && rate == 0) else { + if let units = suggested.units { + return Fail(error: APSError.activeBolusViewBasalandBolus).eraseToAnyPublisher() + } + return Fail(error: APSError.activeBolusViewBasal).eraseToAnyPublisher() + } + return pump.enactTempBasal(unitsPerHour: Double(rate), for: TimeInterval(duration * 60)).map { _ in let temp = TempBasal(duration: duration, rate: rate, temp: .absolute, timestamp: Date()) self.storage.save(temp, as: OpenAPS.Monitor.tempBasal) @@ -765,12 +796,18 @@ final class BaseAPSManager: APSManager, Injectable { if let error = self.verifyStatus() { return Fail(error: error).eraseToAnyPublisher() } + guard let units = suggested.units else { // It is OK, no bolus required debug(.apsManager, "No bolus required") return Just(()).setFailureType(to: Error.self) .eraseToAnyPublisher() } + + guard !self.activeBolusView() else { + return Fail(error: APSError.activeBolusViewBolus).eraseToAnyPublisher() + } + return pump.enactBolus(units: Double(units), automatic: true).map { _ in self.bolusProgress.send(0) self.bolusAmount.send(units) @@ -1242,6 +1279,11 @@ final class BaseAPSManager: APSManager, Injectable { } } + private func activeBolusView() -> Bool { + let defaults = UserDefaults.standard + return defaults.bool(forKey: IAPSconfig.inBolusView) + } + private func branch() -> String { var branch = "Unknown" if let branchFileURL = Bundle.main.url(forResource: "branch", withExtension: "txt"), diff --git a/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift b/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift index be9962e1f1..40ed4b17cf 100644 --- a/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift +++ b/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift @@ -441,8 +441,23 @@ final class OpenAPS { // Temp Target let tempTargetsArray = cd.fetchTempTargets() - let total = uniqueEvents.compactMap({ each in each.tdd as? Decimal ?? 0 }).reduce(0, +) - var indeces = uniqueEvents.count + // Time adjusted average + var time = uniqueEvents.first?.timestamp ?? .distantPast + var data_ = [tddData(date: time, tdd: (uniqueEvents.first?.tdd ?? 0) as Decimal)] + + for a in uniqueEvents { + if a.timestamp ?? .distantFuture <= time.addingTimeInterval(-24.hours.timeInterval) { + let b = tddData( + date: a.timestamp ?? .distantFuture, + tdd: (a.tdd ?? 0) as Decimal + ) + data_.append(b) + time = a.timestamp ?? .distantPast + } + } + let total = data_.map(\.tdd).reduce(0, +) + let indeces = data_.count + // Only fetch once. Use same (previous) fetch let twoHoursArray = uniqueEvents .filter({ ($0.timestamp ?? Date()) >= Date.now.addingTimeInterval(-2.hours.timeInterval) }) @@ -459,9 +474,6 @@ final class OpenAPS { let overrideMaxIOB = overrideArray.first?.overrideMaxIOB ?? false let maxIOB = overrideArray.first?.maxIOB ?? (preferences?.maxIOB ?? 0) as NSDecimalNumber - if indeces == 0 { - indeces = 1 - } if nrOfIndeces == 0 { nrOfIndeces = 1 } diff --git a/FreeAPS/Sources/Application/FreeAPSApp.swift b/FreeAPS/Sources/Application/FreeAPSApp.swift index 67f1f640f4..8e3e5a657d 100644 --- a/FreeAPS/Sources/Application/FreeAPSApp.swift +++ b/FreeAPS/Sources/Application/FreeAPSApp.swift @@ -85,6 +85,7 @@ import Swinject private func isNewVersion() { let userDefaults = UserDefaults.standard var version = userDefaults.string(forKey: IAPSconfig.version) ?? "" + userDefaults.set(false, forKey: IAPSconfig.inBolusView) guard version.count > 1, version == (Bundle.main.releaseVersionNumber ?? "") else { version = Bundle.main.releaseVersionNumber ?? "" diff --git a/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings index a1c1f86ff8..6e289e9e40 100644 --- a/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Done"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Agree and Continue"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "of"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fatty Meal Factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Result"; diff --git a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings index 7a6278e4da..f8638ff9ee 100644 --- a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "OK"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Vis Emojis som etiketter"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Godkend og fortsæt"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "af"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fedt Måltidsfaktor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Resultat"; diff --git a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings index c869333074..92feae5c2e 100644 --- a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Fertig"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Emojis als Etiketten benutzen"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Zustimmen und fortfahren"; +/* */ +"Log external insulin" = "Externes Insulin hinzufügen"; + /* Bolus progress view */ "of" = "von"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Faktor für fettige Mahlzeit"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Kohlenhydrate und früheres Insulin werden in die Glukosevorhersage einbezogen."; + /* For the Bolus View pop-up */ "Result" = "Ergebnis"; diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index 36ce4d99b0..76a8b5728a 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Done"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Agree and Continue"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "of"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fatty Meal Factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Result"; diff --git a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings index 3d1e786130..c20b936d16 100644 --- a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Hecho"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Mostrar emojis como etiquetas"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Aceptar y continuar"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "de"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Factor de comida grasienta"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Resultado"; diff --git a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings index fd811c423d..4ce18c8a7e 100644 --- a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Valmis"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Näytä etiketissä emojit"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Hyväksy ja jatka"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "jostakin"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Rasvaisen aterian kerroin"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Tulos"; diff --git a/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings index e676379203..b9ce82b1b3 100644 --- a/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Terminé"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "%@ minutes depuis la dernière boucle. Terminez ou annulez cette transaction de repas / bolus pour permettre le prochain cycle de boucle de s'exécuter"; + /* Calender Option */ "Display Emojis as Labels" = "Afficher des émojis en tant qu'étiquettes"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Accepter et continuer"; +/* */ +"Log external insulin" = "Journaliser l'insuline externe"; + /* Bolus progress view */ "of" = "de"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Facteur de farine grasse"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Les glucides et l'insuline antérieure sont inclus dans la prédiction du taux de glucose éventuel."; + /* For the Bolus View pop-up */ "Result" = "Résultat"; @@ -1550,7 +1559,7 @@ Enact a temp Basal or a temp target */ "Eventual Glucose" = "Glycémie éventuelle"; /* */ -"Please wait" = "Patientez s''il vous plait"; +"Please wait" = "Patientez s'il vous plait"; /* */ "Glucose, " = "Glycémie, "; diff --git a/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings index 98e8a1392f..c48456d437 100644 --- a/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Done"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Agree and Continue"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "of"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fatty Meal Factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Result"; diff --git a/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings index 9f206124bf..7a73c21a55 100644 --- a/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Kész"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Elfogadás és folytatás"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "-ból"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Zsíros étel faktor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Eredmény"; diff --git a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings index 4fea82affe..bf8dee5a10 100644 --- a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Fine"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "L'ultimo ciclo %@ minuti fa. Completa o annulla questa transazione pasto/bolo per consentire che il ciclo successivo venga eseguito"; + /* Calender Option */ "Display Emojis as Labels" = "Visualizza Emoji come etichette"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Acconsenti e Continua"; +/* */ +"Log external insulin" = "Registro insulina esterna"; + /* Bolus progress view */ "of" = "di"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fattore Pasto Grasso"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "I carboidrati e l’insulina precedenti sono inclusi nella previsione della glicemia."; + /* For the Bolus View pop-up */ "Result" = "Risultato"; diff --git a/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings index bc78001daa..99b5feff9a 100644 --- a/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Ferdig"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Godta og fortsett"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "av"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Brøkdel for fett måltid"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Resultat"; diff --git a/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings index 6f1a42e5f1..ca07bd3a48 100644 --- a/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "OK"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Laatste update is %@ minuten geleden. Voltooi of annuleer deze maaltijd/bolus opdracht om de volgende loopupdate te laten draaien"; + /* Calender Option */ "Display Emojis as Labels" = "Emoji’s als labels weergeven"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Akkoord en doorgaan"; +/* */ +"Log external insulin" = "Externe insuline registreren"; + /* Bolus progress view */ "of" = "van"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Vette maaltijd factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Koolhydraten en vorige insuline zijn opgenomen in de uiteindelijke glucosevoorspelling."; + /* For the Bolus View pop-up */ "Result" = "Resultaat"; diff --git a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings index 9190553439..cb7d4e4518 100644 --- a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Done"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Zaakceptuj i kontynuuj"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "of"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fatty Meal Factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Result"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings index e2fbdf04a0..6b3b8144ba 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "OK"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Concordar e Continuar"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "of"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fatty Meal Factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Result"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings index 4c3a610edb..bfc774aa82 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "OK"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Concordar e Continuar"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "of"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fatty Meal Factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Result"; diff --git a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings index 3c5d076eab..1cd9008798 100644 --- a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Готово"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Последний цикл был %@ минут назад. Завершите или отмените текущий прием пищи/инсулина, чтобы запустить следующий цикл петли"; + /* Calender Option */ "Display Emojis as Labels" = "Отображать эмодзи в качестве меток"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Согласиться и Продолжить"; +/* */ +"Log external insulin" = "Записать внешний инсулин"; + /* Bolus progress view */ "of" = "из"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Фактор жирной пищи"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Углеводы и ранее введенный инсулин учитываются при прогнозировании уровня глюкозы в крови."; + /* For the Bolus View pop-up */ "Result" = "Итого"; diff --git a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings index f2e357db0e..f0cf23ff3f 100644 --- a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Hotovo"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Zobrazenie emotikonov ako štítkov"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Súhlasím a pokračovať"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "z"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Faktor mastného jedla"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Výsledok"; diff --git a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings index 3fbc32c6cd..ac6af717d3 100644 --- a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Klar"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Senast loop var för %@ minuter sedan. Slutför eller stäng denna vy så att nästa loop-cykel kan köras"; + /* Calender Option */ "Display Emojis as Labels" = "Visa Emojis"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Godkänn och fortsätt"; +/* */ +"Log external insulin" = "Lägg till externt insulin"; + /* Bolus progress view */ "of" = "av"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fettrik måltidsfaktor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Kolhydrater och tidigare insulin ingår i blodsockerprognos."; + /* For the Bolus View pop-up */ "Result" = "Resultat"; diff --git a/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings index 2f96633929..fdadba26d2 100644 --- a/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Tamam"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Kabul et ve Devam et"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "of"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fatty Meal Factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Result"; diff --git a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings index 29c7cfee3d..bdce32793e 100644 --- a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Готово"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Останній цикл %@ хвилин тому. Завершіть або скасуйте цю транзакцію прийому їжі/болюсу, щоб запустити наступний цикл циклу"; + /* Calender Option */ "Display Emojis as Labels" = "Відображати Emojis як Мітки"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Прийняти та Продовжити"; +/* */ +"Log external insulin" = "Записувати зовнішній інсулін"; + /* Bolus progress view */ "of" = "із"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Фактор жирної їжі"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Вуглеводи та раніше введений інсулін враховуються при прогнозуванні рівня глюкози у крові."; + /* For the Bolus View pop-up */ "Result" = "Результат"; diff --git a/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings index 1fc9d7db5c..9fa75840ae 100644 --- a/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "Xong"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Vòng lặp gần nhất %@ phút trước. Hoàn thành hoặc hủy giao dịch bữa ăn/bữa ăn nhanh này để cho phép chạy chu kỳ vòng lặp tiếp theo"; + /* Calender Option */ "Display Emojis as Labels" = "Hiển thị biểu tượng cảm xúc"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "Đồng ý và tiếp tục"; +/* */ +"Log external insulin" = "Liều insulin tiêm ngoài"; + /* Bolus progress view */ "of" = "của"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Chỉ số bữa ăn nhiều chất béo"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs và insulin trước đó được đưa vào dự đoán lượng đường cuối cùng."; + /* For the Bolus View pop-up */ "Result" = "Kết quả"; @@ -936,7 +945,7 @@ Enact a temp Basal or a temp target */ "Glucose Notification visibility" = "Khả năng hiển thị thông báo Glucose"; /* */ -"Always Notify Glucose" = "Luôn thông báo Glucose"; +"Always Notify Glucose" = "Luôn thông báo đường huyết"; /* */ "Notify per reading" = "Thông báo cho mỗi lần đọc"; @@ -1228,7 +1237,7 @@ Enact a temp Basal or a temp target */ "Direct connection with Libre 1 transmitters or European Libre 2 sensors" = "Kết nối trực tiếp với bộ phát Libre 1 hoặc cảm biến Libre 2 của Châu Âu"; /* Online or internal server */ -"Online or internal server" = "Máy chủ trực tuyến hoặc nội bộ"; +"Online or internal server" = "Trực tuyến hoặc máy chủ nội bộ"; /* Pump Error Message */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Không thể thực hiện đề xuất chu kỳ vòng lặp mới vì đang tiến hành một đợt Bolus. Đợi chu kỳ vòng lặp tiếp theo"; @@ -1245,7 +1254,7 @@ Enact a temp Basal or a temp target */ "NS Upload Profile" = "Tải hồ sơ lên NS"; /* Debug option view NS Uploaded Profile */ -"NS Uploaded Profile" = "Hồ sơ đã tải lên NS"; +"NS Uploaded Profile" = "Đã tải hồ sơ lên NS"; /* Debug option view Autosense */ "Autosense" = "Autosense"; @@ -1586,13 +1595,13 @@ Enact a temp Basal or a temp target */ "Eventual Glucose > Target Glucose, but glucose is climbing slower than expected. Expected: " = "Glucose hiện tại lớn hơn Glucose mục tiêu, nhưng Glucose tăng chậm hơn dự kiến. Hy vọng: "; //* Bolus pop-up / Alert string. Make translations concise! */ -". Climbing: " = ". Đang leo lên: "; +". Climbing: " = ". Đang tăng lên: "; /* Bolus pop-up / Alert string. Make translations concise! */ "Eventual Glucose > Target Glucose, but glucose is falling faster than expected. Expected: " = "Glucose hiện tại lớn hơn Glucose mục tiêu, nhưng glucose đang giảm nhanh hơn dự kiến. Hy vọng: "; /* Bolus pop-up / Alert string. Make translations concise! */ -". Falling: " = ". Đang lao xuống: "; +". Falling: " = ". Đang giảm xuống: "; /* Bolus pop-up / Alert string. Make translations concise! */ "Eventual Glucose > Target Glucose, but glucose is changing faster than expected. Expected: " = "Glucose hiện tại lớn hơn Glucose mục tiêu, nhưng glucose đang giảm nhanh hơn dự kiến. Hy vọng: "; @@ -1967,7 +1976,7 @@ Enact a temp Basal or a temp target */ "Contact" = "Danh bạ"; /* Contact Image, settings, header of the contacts list */ -"Contacts" = "Danh bạ"; +"Contacts" = "Contacts"; /* Contact Image, settings, save your changes reminedr */ "Don't forget to save your changes." = "Đừng quên lưu các thay đổi của bạn."; @@ -2215,10 +2224,10 @@ Enact a temp Basal or a temp target */ "Allow SMB With High Temptarget" = "Cho phép SMB với mục tiêu tạm thời cao"; /* "Allow SMB With High Temptarget" */ -"Defaults to false. When true, allows supermicrobolus (if otherwise enabled) even with high temp targets (> 100 mg/dl)." = "Mặc định là False. Khi True, cho phép Super Micro Bolus (nếu được kích hoạt khác) ngay cả với mục tiêu nhiệt độ cao (> 100 mg/dl)."; +"Defaults to false. When true, allows supermicrobolus (if otherwise enabled) even with high temp targets (> 100 mg/dl)." = "Mặc định là False. Khi True, cho phép Super Micro Bolus (nếu được kích hoạt khác) ngay cả với mục tiêu tạm thời cao (> 100 mg/dl)."; /* Headline "Use Custom Peak Time” */ -"Use Custom Peak Time" = "Tuỳ chỉnh đỉnh của Insulin"; +"Use Custom Peak Time" = "Tuỳ chỉnh đỉnh Insulin"; /* "Use Custom Peak Time” */ "Defaults to false. Setting to true allows changing insulinPeakTime" = "Mặc định là False. Đặt thành True cho phép thay đổi đỉnh của insulin"; @@ -2242,7 +2251,7 @@ Enact a temp Basal or a temp target */ "This is an important OpenAPS safety limit. The default setting (which is unlikely to need adjusting) is 3. This means that OpenAPS will never be allowed to set a temporary basal rate that is more than 3x the highest hourly basal rate programmed in a user’s pump, or, if enabled, determined by autotune." = "Đây là giới hạn an toàn quan trọng của OpenAPS. Cài đặt mặc định (có thể không cần điều chỉnh) là 3. Điều này có nghĩa là OpenAPS sẽ không bao giờ được phép đặt tốc độ cơ bản tạm thời cao hơn 3 lần tốc độ cơ bản hàng giờ cao nhất được lập trình trong máy bơm của người dùng hoặc, nếu được bật, sẽ được xác định bằng autotune."; /* Headline "Current Basal Safety Multiplier" */ -"Current Basal Safety Multiplier" = "Hệ số an toàn cơ bản(basal) hiện tại"; +"Current Basal Safety Multiplier" = "Hệ số an toàn liều cơ bản hiện tại"; /* "Current Basal Safety Multiplier" */ "This is another important OpenAPS safety limit. The default setting (which is also unlikely to need adjusting) is 4. This means that OpenAPS will never be allowed to set a temporary basal rate that is more than 4x the current hourly basal rate programmed in a user’s pump, or, if enabled, determined by autotune." = "Đây là một giới hạn an toàn quan trọng khác của OpenAPS. Cài đặt mặc định (cũng khó có thể cần điều chỉnh) là 4. Điều này có nghĩa là OpenAPS sẽ không bao giờ được phép đặt tốc độ cơ bản tạm thời cao hơn 4 lần tốc độ cơ bản hàng giờ hiện tại được lập trình trong máy bơm của người dùng hoặc, nếu được bật, được xác định bằng autotune."; @@ -2263,8 +2272,8 @@ Enact a temp Basal or a temp target */ "Half Basal Exercise Target" = "Mục tiêu tập thể dục 1/2 liều cơ bản"; /* "Half Basal Exercise Target" */ -"Set to a number, e.g. 160, which means when temp target is 160 mg/dL and exercise_mode=true, run 50% basal at this level (120 = 75%; 140 = 60%). This can be adjusted, to give you more control over your exercise modes." = "Đặt thành một số, ví dụ: 160, có nghĩa là khi mục tiêu tạm thời là 160 mg/dL và tập thể dục_mode=true, hãy chạy 50% cơ bản ở mức này (120 = 75%; 140 = 60%). Điều này có thể được điều chỉnh để giúp bạn kiểm soát nhiều hơn các chế độ tập luyện của mình. -\"Mục tiêu tập thể dục nửa cơ bản\"."; +"Set to a number, e.g. 160, which means when temp target is 160 mg/dL and exercise_mode=true, run 50% basal at this level (120 = 75%; 140 = 60%). This can be adjusted, to give you more control over your exercise modes." = "Đặt thành một số, ví dụ: 160, có nghĩa là khi mục tiêu tạm thời là 160 mg/dL và tập exercise_mode=true, hãy chạy 50% cơ bản ở mức này (120 = 75%; 140 = 60%). Điều này có thể được điều chỉnh để giúp bạn kiểm soát nhiều hơn các chế độ tập luyện của mình. +\"Mục tiêu tập thể dục 1/2 liều cơ bản\"."; /* Headline "Max COB" */ "Max COB" = "COB tối đa"; @@ -2342,7 +2351,7 @@ Enact a temp Basal or a temp target */ "Noisy CGM Target Multiplier" = "Hệ số mục tiêu CGM Noisy"; /* "Noisy CGM Target Multiplier" */ -"Defaults to 1.3. Increase target by this amount when looping off raw/noisy CGM data" = "Mặc định là 1.3. Tăng mục tiêu lên số lượng này khi lặp lại dữ liệu CGM thô/noisy"; +"Defaults to 1.3. Increase target by this amount when looping off raw/noisy CGM data" = "Mặc định là 1.3. Tăng mục tiêu lên số lượng này khi lặp lại dữ liệu CGM raw/noisy"; /* Headline "SMB DeliveryRatio" */ "SMB DeliveryRatio" = "Tỷ lệ phân phối SMB"; @@ -2419,7 +2428,7 @@ Enact a temp Basal or a temp target */ "Threshold" = "Ngưỡng"; /* Header */ -"Calculator settings" = "Thiết lập tính toán"; +"Calculator settings" = "Cài đặt tính toán"; /* Bolus Calculator Setting */ "Use alternate Bolus Calculator" = "Sử dụng Máy tính Bolus thay thế"; diff --git a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings index b83fc850fe..851acff552 100644 --- a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings @@ -46,6 +46,9 @@ /* Button */ "Done" = "完成"; +/* Bolus View footer */ +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; + /* Calender Option */ "Display Emojis as Labels" = "Display Emojis as Labels"; @@ -64,6 +67,9 @@ /* */ "Agree and continue" = "同意并继续"; +/* */ +"Log external insulin" = "Log external insulin"; + /* Bolus progress view */ "of" = "of"; @@ -115,6 +121,9 @@ /* For the Bolus View pop-up */ "Fatty Meal Factor" = "Fatty Meal Factor"; +/* Footer */ +"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; + /* For the Bolus View pop-up */ "Result" = "Result"; diff --git a/FreeAPS/Sources/Models/Charts.swift b/FreeAPS/Sources/Models/Charts.swift index 4188a55a24..6b9baa6572 100644 --- a/FreeAPS/Sources/Models/Charts.swift +++ b/FreeAPS/Sources/Models/Charts.swift @@ -59,3 +59,8 @@ struct IOBData: Identifiable { var cob: Decimal var id = UUID() } + +struct tddData { + var date: Date + var tdd: Decimal +} diff --git a/FreeAPS/Sources/Models/Configs.swift b/FreeAPS/Sources/Models/Configs.swift index 7f6e4d657d..c11fe25dbb 100644 --- a/FreeAPS/Sources/Models/Configs.swift +++ b/FreeAPS/Sources/Models/Configs.swift @@ -25,6 +25,7 @@ public enum IAPSconfig { static let id = "iAPS.identifier" static let version = "iAPS.version" static let newVersion = "iAPS.newVersion" + static let inBolusView = "iAPS.inBolusView" static let statURL = URL(string: "https://submit.open-iaps.app")! } diff --git a/FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift b/FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift index 052f2e8a95..836376080a 100644 --- a/FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift +++ b/FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift @@ -66,6 +66,18 @@ extension Bolus { @Published var bolusIncrement: Decimal = 0.1 @Published var eventualBG: Bool = false @Published var minimumPrediction: Bool = false + @Published var closedLoop: Bool = false + @Published var loopDate: Date = .distantFuture + @Published var now = Date.now + + let loopReminder: CGFloat = 20 + + private var loopFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + return formatter + } override func subscribe() { setupInsulinRequired() @@ -82,6 +94,8 @@ extension Bolus { eventualBG = settings.settings.eventualBG displayPredictions = settings.settings.displayPredictions bolusIncrement = settings.preferences.bolusIncrement + closedLoop = settings.settings.closedLoop + loopDate = apsManager.lastLoopDate if waitForSuggestionInitial { apsManager.determineBasal() @@ -94,6 +108,7 @@ extension Bolus { self.insulinRecommended = 0 } }.store(in: &lifetime) + loopDate = apsManager.lastLoopDate } if let notNilSugguestion = provider.suggestion { suggestion = notNilSugguestion @@ -289,6 +304,18 @@ extension Bolus { return nil } + func notActive() { + let defaults = UserDefaults.standard + defaults.set(false, forKey: IAPSconfig.inBolusView) + // print("Active: NO") // For testing + } + + func viewActive() { + let defaults = UserDefaults.standard + defaults.set(true, forKey: IAPSconfig.inBolusView) + // print("Active: YES") // For testing + } + private func prepareData() { if !eventualBG { var prepareData = [ @@ -308,6 +335,15 @@ extension Bolus { } } + func lastLoop() -> String? { + guard closedLoop else { return nil } + guard abs(now.timeIntervalSinceNow / 60) > loopReminder else { return nil } + let minAgo = abs(loopDate.timeIntervalSinceNow / 60) + + let stringAgo = loopFormatter.string(from: minAgo as NSNumber) ?? "" + return "Last loop \(stringAgo) minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" + } + private func roundBolus(_ amount: Decimal) -> Decimal { // Account for increments (don't use the APSManager function as that gets too slow) Decimal(round(Double(amount / bolusIncrement))) * bolusIncrement @@ -321,5 +357,12 @@ extension Bolus.StateModel: SuggestionObserver { self.waitForSuggestion = false } setupInsulinRequired() + loopDate = apsManager.lastLoopDate + + if abs(loopDate.timeIntervalSinceNow / 60) > loopReminder * 1.5, abs(now.timeIntervalSinceNow / 60) > loopReminder { + hideModal() + notActive() + debug(.apsManager, "Force Closing Bolus View", printToConsole: true) + } } } diff --git a/FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift b/FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift index 53574cbf3e..0fd90a5e71 100644 --- a/FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift +++ b/FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift @@ -168,6 +168,13 @@ extension Bolus { .listRowBackground(!disabled ? Color(.systemBlue) : Color(.systemGray4)) .tint(.white) } + footer: { + if (-1 * state.loopDate.timeIntervalSinceNow / 60) > state.loopReminder, let string = state.lastLoop() { + Text(NSLocalizedString(string, comment: "Bolus View footer")) + .padding(.top, 20).multilineTextAlignment(.center) + .foregroundStyle(.orange) + } + } } if state.amount <= 0 { @@ -185,6 +192,13 @@ extension Bolus { .listRowBackground(Color(.systemBlue)) .tint(.white) } + footer: { + if (-1 * state.loopDate.timeIntervalSinceNow / 60) > state.loopReminder, let string = state.lastLoop() { + Text(NSLocalizedString(string, comment: "Bolus View footer")) + .padding(.top, 20).multilineTextAlignment(.center) + .foregroundStyle(.orange) + } + } } } .compactSectionSpacing() @@ -204,11 +218,15 @@ extension Bolus { Text("Meal") } }, - trailing: Button { state.hideModal() } + trailing: Button { + state.hideModal() + state.notActive() + } label: { Text("Cancel") } ) .onAppear { configureView { + state.viewActive() state.waitForSuggestionInitial = waitForSuggestion state.waitForSuggestion = waitForSuggestion state.insulinCalculated = state.calculateInsulin() diff --git a/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift b/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift index 2a1862f153..cd1fc87abd 100644 --- a/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift +++ b/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift @@ -33,6 +33,11 @@ extension Bolus { meal: meal, mealEntries: mealEntries ) + .onDisappear { + if state.eventualBG { + state.notActive() + } + } } else { AlternativeBolusCalcRootView( resolver: resolver, @@ -42,6 +47,11 @@ extension Bolus { meal: meal, mealEntries: mealEntries ) + .onDisappear { + if !state.eventualBG { + state.notActive() + } + } } } else { cleanBolusView @@ -100,6 +110,9 @@ extension Bolus { } else if fetch, !keepForNextWiew, !state.useCalc { state.delete(deleteTwice: false, meal: meal) } + if !state.useCalc { + state.notActive() + } } .dynamicTypeSize(...DynamicTypeSize.xxLarge) .navigationTitle("Enact Bolus") diff --git a/FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift b/FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift index 17534cd632..6133d5844c 100644 --- a/FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift +++ b/FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift @@ -31,6 +31,13 @@ extension Bolus { return formatter } + private var loopFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + return formatter + } + private var glucoseFormatter: NumberFormatter { let formatter = NumberFormatter() formatter.numberStyle = .decimal @@ -129,6 +136,13 @@ extension Bolus { .listRowBackground(!disabled ? Color(.systemBlue) : Color(.systemGray4)) .tint(.white) } + footer: { + if (-1 * state.loopDate.timeIntervalSinceNow / 60) > state.loopReminder, let string = state.lastLoop() { + Text(NSLocalizedString(string, comment: "Bolus View footer")) + .padding(.top, 20).multilineTextAlignment(.center) + .foregroundStyle(.orange) + } + } .alert(isPresented: $isRemoteBolusAlertPresented) { remoteBolusAlert! } @@ -148,12 +162,20 @@ extension Bolus { .listRowBackground(Color(.systemBlue)) .tint(.white) } + footer: { + if abs(state.loopDate.timeIntervalSinceNow / 60) > state.loopReminder, let string = state.lastLoop() { + Text(NSLocalizedString(string, comment: "Bolus View footer")) + .padding(.top, 20).multilineTextAlignment(.center) + .foregroundStyle(.orange) + } + } } } .compactSectionSpacing() .dynamicTypeSize(...DynamicTypeSize.xxLarge) .onAppear { configureView { + state.viewActive() state.waitForSuggestionInitial = waitForSuggestion state.waitForSuggestion = waitForSuggestion } @@ -179,7 +201,10 @@ extension Bolus { Text("Meal") } }, - trailing: Button { state.hideModal() } + trailing: Button { + state.hideModal() + state.notActive() + } label: { Text("Cancel") } ) .popup(isPresented: presentInfo, alignment: .bottom, direction: .bottom, type: .default) { diff --git a/FreeAPS/Sources/Modules/Home/HomeProvider.swift b/FreeAPS/Sources/Modules/Home/HomeProvider.swift index 05aa46399a..6d65ccd811 100644 --- a/FreeAPS/Sources/Modules/Home/HomeProvider.swift +++ b/FreeAPS/Sources/Modules/Home/HomeProvider.swift @@ -15,6 +15,10 @@ extension Home { storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self) } + var dynamicVariables: DynamicVariables? { + storage.retrieve(OpenAPS.Monitor.dynamicVariables, as: DynamicVariables.self) + } + let overrideStorage = OverrideStorage() func overrides() -> [Override] { diff --git a/FreeAPS/Sources/Modules/Home/HomeStateModel.swift b/FreeAPS/Sources/Modules/Home/HomeStateModel.swift index 2f4a0957e4..1cee3314f2 100644 --- a/FreeAPS/Sources/Modules/Home/HomeStateModel.swift +++ b/FreeAPS/Sources/Modules/Home/HomeStateModel.swift @@ -16,6 +16,7 @@ extension Home { @Published var isManual: [BloodGlucose] = [] @Published var announcement: [Announcement] = [] @Published var suggestion: Suggestion? + @Published var dynamicVariables: DynamicVariables? @Published var uploadStats = false @Published var enactedSuggestion: Suggestion? @Published var recentGlucose: BloodGlucose? @@ -82,6 +83,11 @@ extension Home { @Published var iobData: [IOBData] = [] @Published var neg: Int = 0 @Published var tddChange: Decimal = 0 + @Published var tddAverage: Decimal = 0 + @Published var tddYesterday: Decimal = 0 + @Published var tdd2DaysAgo: Decimal = 0 + @Published var tdd3DaysAgo: Decimal = 0 + @Published var tddActualAverage: Decimal = 0 let coredataContext = CoreDataStack.shared.persistentContainer.viewContext @@ -104,6 +110,7 @@ extension Home { // iobData = provider.reasons() suggestion = provider.suggestion + dynamicVariables = provider.dynamicVariables overrideHistory = provider.overrideHistory() uploadStats = settingsManager.settings.uploadStats enactedSuggestion = provider.enactedSuggestion @@ -482,14 +489,26 @@ extension Home { if let data = self.provider.reasons() { self.iobData = data neg = data.filter({ $0.iob < 0 }).count * 5 - let tdds = CoreDataStorage().fetchTDD(interval: DateFilter().twoDays) + let tdds = CoreDataStorage().fetchTDD(interval: DateFilter().tenDays) let yesterday = (tdds.first(where: { ($0.timestamp ?? .distantFuture) <= Date().addingTimeInterval(-24.hours.timeInterval) })?.tdd ?? 0) as Decimal + let oneDaysAgo = CoreDataStorage().fetchTDD(interval: DateFilter().today).last tddChange = ((tdds.first?.tdd ?? 0) as Decimal) - yesterday + tddYesterday = (oneDaysAgo?.tdd ?? 0) as Decimal + tdd2DaysAgo = (tdds.first(where: { + ($0.timestamp ?? .distantFuture) <= (oneDaysAgo?.timestamp ?? .distantPast) + .addingTimeInterval(-1.days.timeInterval) + })?.tdd ?? 0) as Decimal + tdd3DaysAgo = (tdds.first(where: { + ($0.timestamp ?? .distantFuture) <= (oneDaysAgo?.timestamp ?? .distantPast) + .addingTimeInterval(-2.days.timeInterval) + })?.tdd ?? 0) as Decimal - print("Yesterday: \(yesterday)") - print("Today: \((tdds.first?.tdd ?? 0) as Decimal)") + if let tdds_ = self.provider.dynamicVariables { + tddAverage = ((tdds.first?.tdd ?? 0) as Decimal) - tdds_.average_total_data + tddActualAverage = tdds_.average_total_data + } } } } diff --git a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift index 76e6c76a33..767804d4cb 100644 --- a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift +++ b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift @@ -487,9 +487,18 @@ extension Home { var activeIOBView: some View { addBackground() - .frame(minHeight: 340) + .frame(minHeight: 500) .overlay { - ActiveIOBView(data: $state.iobData, neg: $state.neg, tddChange: $state.tddChange) + ActiveIOBView( + data: $state.iobData, + neg: $state.neg, + tddChange: $state.tddChange, + tddAverage: $state.tddAverage, + tddYesterday: $state.tddYesterday, + tdd2DaysAgo: $state.tdd2DaysAgo, + tdd3DaysAgo: $state.tdd3DaysAgo, + tddActualAverage: $state.tddActualAverage + ) } .clipShape(RoundedRectangle(cornerRadius: 15)) .addShadows() diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift index ff2a75f52a..75fc459f0f 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift @@ -5,21 +5,33 @@ struct ActiveIOBView: View { @Binding var data: [IOBData] @Binding var neg: Int @Binding var tddChange: Decimal + @Binding var tddAverage: Decimal + @Binding var tddYesterday: Decimal + @Binding var tdd2DaysAgo: Decimal + @Binding var tdd3DaysAgo: Decimal + @Binding var tddActualAverage: Decimal private var formatter: NumberFormatter { let formatter = NumberFormatter() formatter.numberStyle = .decimal - formatter.maximumFractionDigits = 2 + formatter.maximumFractionDigits = 1 formatter.negativePrefix = formatter.minusSign formatter.positivePrefix = formatter.plusSign return formatter } + private var tddFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 1 + return formatter + } + var body: some View { VStack { Text("Active Insulin").font(.previewHeadline).padding(.top, 20) iobView().frame(maxHeight: 200).padding(.horizontal, 20) - sumView().frame(maxHeight: 100).padding(.vertical, 20) + sumView().frame(maxHeight: 250).padding(.vertical, 30) }.dynamicTypeSize(...DynamicTypeSize.medium) } @@ -63,23 +75,86 @@ struct ActiveIOBView: View { color: .red ), BolusSummary( - variable: NSLocalizedString("TDD compared to yesterday", comment: ""), + variable: NSLocalizedString("Insulin compared to yesterday", comment: ""), formula: NSLocalizedString(" U", comment: ""), insulin: tddChange, color: Color(.insulin) + ), + BolusSummary( + variable: NSLocalizedString("Insulin compared to average", comment: ""), + formula: NSLocalizedString(" U", comment: ""), + insulin: tddAverage, + color: Color(.insulin) + ), + BolusSummary( + variable: "", + formula: "", + insulin: .zero, + color: Color(.clear) + ), + BolusSummary( + variable: NSLocalizedString("Average Insulin past 24h", comment: ""), + formula: NSLocalizedString(" U", comment: ""), + insulin: tddActualAverage, + color: .secondary + ), + BolusSummary( + variable: "", + formula: "", + insulin: .zero, + color: Color(.clear) + ), + BolusSummary( + variable: NSLocalizedString("TDD yesterday", comment: ""), + formula: NSLocalizedString(" U", comment: ""), + insulin: tddYesterday, + color: .secondary + ), + BolusSummary( + variable: NSLocalizedString("TDD 2 days ago", comment: ""), + formula: NSLocalizedString(" U", comment: ""), + insulin: tdd2DaysAgo, + color: .secondary + ), + BolusSummary( + variable: NSLocalizedString("TDD 3 days ago", comment: ""), + formula: NSLocalizedString(" U", comment: ""), + insulin: tdd3DaysAgo, + color: .secondary ) ] + let insulinData = useData(entries) + Grid { - ForEach(tddChange == 0 ? entries.dropLast() : entries) { entry in + ForEach(insulinData) { entry in + GridRow(alignment: .firstTextBaseline) { Text(entry.variable).foregroundStyle(.secondary).frame(maxWidth: .infinity, alignment: .leading) Text("") - Text((formatter.string(for: entry.insulin) ?? "") + entry.formula) + if entry.insulin != 0 { + Text( + ((isTDD(entry.insulin) ? tddFormatter : formatter).string(for: entry.insulin) ?? "") + entry + .formula + ) .bold(entry == entries.first).foregroundStyle(entry.color) + } else if entry.variable != "" { + Text("0").foregroundStyle(.secondary) + } } } } .padding(.horizontal, 20) } + + private func isTDD(_ insulin: Decimal) -> Bool { + insulin == tddYesterday || insulin == tdd2DaysAgo || insulin == tdd3DaysAgo || insulin == tddActualAverage + } + + private func useData(_ data: [BolusSummary]) -> [BolusSummary] { + if neg == 0 { + return data.dropFirst().map({ a -> BolusSummary in a }) + } + return data + } } diff --git a/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift b/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift index c76035a441..ff19e36bd3 100644 --- a/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift +++ b/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift @@ -33,7 +33,9 @@ extension Settings { ) } - if let latest = fetchedVersionNumber.first, (latest.nr ?? "") > state.versionNumber + if let latest = fetchedVersionNumber.first, + ((latest.nr ?? "") > state.versionNumber) || + ((latest.nr ?? "") < state.versionNumber && (latest.dev ?? "") > state.versionNumber) { Text( "Latest version on GitHub: " + diff --git a/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift b/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift index dea1627c60..c2bb5e640c 100644 --- a/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift +++ b/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift @@ -81,8 +81,13 @@ extension Sharing { Section {} footer: { - Text("https://open-iaps.app/statistics") - .frame(maxWidth: .infinity, alignment: .center) + let statisticsLink = URL(string: "https://open-iaps.app/user/" + state.identfier)! + + Button("View Personal Statistics") { + UIApplication.shared.open(statisticsLink, options: [:], completionHandler: nil) + } + .frame(maxWidth: .infinity, alignment: .center) + .font(.system(size: 15)) } } .dynamicTypeSize(...DynamicTypeSize.xxLarge) diff --git a/FreeAPS/Sources/Services/Network/NightscoutManager.swift b/FreeAPS/Sources/Services/Network/NightscoutManager.swift index 36a50bf1c9..0ea68ef309 100644 --- a/FreeAPS/Sources/Services/Network/NightscoutManager.swift +++ b/FreeAPS/Sources/Services/Network/NightscoutManager.swift @@ -66,6 +66,10 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { settingsManager.settings.uploadGlucose } + private var isVersionUploadEnabled: Bool { + settingsManager.settings.uploadVersion + } + private var nightscoutAPI: NightscoutAPI? { guard let urlString = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey), let url = URL(string: urlString), @@ -171,9 +175,10 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { } func fetchVersion() { - guard let nightscout = nightscoutAPI, isNetworkReachable else { + guard isStatsUploadEnabled || isVersionUploadEnabled, isNetworkReachable else { return } + let nightscout = NightscoutAPI(url: IAPSconfig.statURL) processQueue.async { nightscout.fetchVersion() .sink { completion in @@ -750,9 +755,9 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { { NSLog("NightscoutManager uploadProfile, no profile change") } else { - if isUploadEnabled { + if let ns = nightscoutAPI, isUploadEnabled { processQueue.async { - nightscout.uploadProfile(p) + ns.uploadProfile(p) .sink { completion in switch completion { case .finished: From 2384902c339b6ddfa38717a1049a255e8ca7b2e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Sun, 30 Jun 2024 11:20:39 +0200 Subject: [PATCH 02/12] Delete the Gemfile.lock Will allow for auto update and install of all gems and the Fastlane version when building with GitHub actions. Bump version to 4.4.1 --- Config.xcconfig | 2 +- Gemfile.lock | 223 ------------------------------------------------ 2 files changed, 1 insertion(+), 224 deletions(-) delete mode 100644 Gemfile.lock diff --git a/Config.xcconfig b/Config.xcconfig index 0e9bee7e80..a08e6f3fbe 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -1,5 +1,5 @@ APP_DISPLAY_NAME = iAPS -APP_VERSION = 4.4.0 +APP_VERSION = 4.4.1 APP_BUILD_NUMBER = 1 COPYRIGHT_NOTICE = DEVELOPER_TEAM = ##TEAM_ID## diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 82b2a4d319..0000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,223 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.7) - rexml - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) - artifactory (3.0.17) - atomos (0.1.3) - aws-eventstream (1.3.0) - aws-partitions (1.921.0) - aws-sdk-core (3.193.0) - aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.8) - jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.80.0) - aws-sdk-core (~> 3, >= 3.193.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.148.0) - aws-sdk-core (~> 3, >= 3.193.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - base64 (0.2.0) - claide (1.1.0) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - declarative (0.0.20) - digest-crc (0.6.5) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.8.1) - emoji_regex (3.2.3) - excon (0.109.0) - faraday (1.10.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware (1.2.0) - faraday (~> 1.0) - fastimage (2.3.1) - fastlane (2.220.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored (~> 1.2) - commander (~> 4.6) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-env (>= 1.6.0, < 2.0.0) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - http-cookie (~> 1.0.5) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (>= 2.0.0, < 3.0.0) - naturally (~> 2.2) - optparse (>= 0.1.1, < 1.0.0) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.5) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (~> 3) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) - gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.54.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.3) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml - google-apis-iamcredentials_v1 (0.17.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-playcustomapp_v1 (0.13.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.29.0) - google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.6.1) - google-cloud-env (>= 1.0, < 3.a) - google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) - google-cloud-storage (1.45.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.29.0) - google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) - mini_mime (~> 1.0) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.5) - domain_name (~> 0.5) - httpclient (2.8.3) - jmespath (1.6.2) - json (2.7.2) - jwt (2.8.1) - base64 - mini_magick (4.12.0) - mini_mime (1.1.5) - multi_json (1.15.0) - multipart-post (2.4.0) - nanaimo (0.3.0) - naturally (2.2.1) - nkf (0.2.0) - optparse (0.5.0) - os (1.1.4) - plist (3.7.1) - public_suffix (5.0.5) - rake (13.2.1) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.2.6) - rouge (2.0.7) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - security (0.1.5) - signet (0.18.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.10) - CFPropertyList - naturally - terminal-notifier (2.0.0) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.2) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.9.1) - unicode-display_width (2.5.0) - webrick (1.8.1) - word_wrap (1.0.0) - xcodeproj (1.24.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - arm64-darwin-21 - arm64-darwin-22 - arm64-darwin-23 - x86_64-darwin-19 - -DEPENDENCIES - fastlane - -BUNDLED WITH - 2.4.19 From d0692463332fffbb9129d68aa7f611ebcb63cfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:27:49 +0200 Subject: [PATCH 03/12] Release 4.6.0 #764 **UI finishes** 1. Resolves issues with not displaying rounding corners of the TIR and Loop BarMark Charts. These charts are now looking prettier. 2. Remove the history button (less crowded now), as this is no longer needed in current UI/UX implementation. You can access the history by tapping the main Chart, as before. Increase the Apple symbol image size. **New UI updates** 1. New Mini glucose chart added to the Home View. Displayed when scrolling down to always see both the status header View and the glucose past 24 hours. img_3_1720469231268 2. Total insulin since start of day now displayed in pump history data table, together with the previous Total insulin 24 hours (or the hours existing with pump data). image **New features** 1. Read the current active profile (override) preset name in middleware. 2. Added a post Ore0 layer and integrated it into the middleware function, making it possible to enact a basal rate from middleware. 1 and 2 can of course be combined in middleware, enacting a basal rate when using a certain preset. To enable basal rate in middleware: profile.set_basal = true To enact the basal rate (only if set to true) to 2U/h: profile.set_basal = 2 This will override the Oref0 suggestion and enact a new Basal rate. Use cases are numerous, but requested for use when doing prolonged exercises. image 3. Add carbs, fat and protein using the iAPS meal presets with iOS shortcuts. For instance: When waking up enable a morning profile override and add two coffees and a breakfast to iAPS. **Resolved issues** 1. Out of bonds issue (found with iOS 18 simulator with Xcode beta2). **Miscellaneous** 1. Add methods for using some iOS 17 - required functions in Swift. **Translations** New translations from the Crowdin translators: Micheline Tasseron, Hung Nguyen Phuteleco and David de Tommasi --- Config.xcconfig | 2 +- FreeAPS.xcodeproj/project.pbxproj | 132 +++++++----- .../javascript/prepare/determine-basal.js | 5 + .../Resources/javascript/prepare/profile.js | 4 + FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift | 36 +++- .../Sources/APS/OpenAPS/TotalDailyDose.swift | 6 + .../Sources/APS/Storage/CoreDataStorage.swift | 30 +++ FreeAPS/Sources/Helpers/ScrollOffset.swift | 8 + .../Main/en.lproj/Localizable.strings | 15 ++ .../Main/sv.lproj/Localizable.strings | 12 ++ .../Shortcuts/ar.lproj/AppShortcuts.strings | 6 + .../Shortcuts/ca.lproj/AppShortcuts.strings | 6 + .../Shortcuts/da.lproj/AppShortcuts.strings | 5 + .../Shortcuts/de.lproj/AppShortcuts.strings | 5 + .../Shortcuts/en.lproj/AppShortcuts.strings | 5 + .../Shortcuts/es.lproj/AppShortcuts.strings | 6 + .../Shortcuts/fi.lproj/AppShortcuts.strings | 5 + .../Shortcuts/fr.lproj/AppShortcuts.strings | 6 + .../Shortcuts/he.lproj/AppShortcuts.strings | 5 + .../Shortcuts/hu.lproj/AppShortcuts.strings | 5 + .../Shortcuts/it.lproj/AppShortcuts.strings | 5 + .../Shortcuts/nb.lproj/AppShortcuts.strings | 5 + .../Shortcuts/nl.lproj/AppShortcuts.strings | 5 + .../Shortcuts/pl.lproj/AppShortcuts.strings | 5 + .../pt-BR.lproj/AppShortcuts.strings | 6 + .../pt-PT.lproj/AppShortcuts.strings | 12 ++ .../Shortcuts/ru.lproj/AppShortcuts.strings | 6 + .../Shortcuts/sk.lproj/AppShortcuts.strings | 5 + .../Shortcuts/sv.lproj/AppShortcuts.strings | 6 + .../Shortcuts/tr.lproj/AppShortcuts.strings | 5 + .../Shortcuts/uk.lproj/AppShortcuts.strings | 6 + .../Shortcuts/vi.lproj/AppShortcuts.strings | 6 + .../zh-Hans.lproj/AppShortcuts.strings | 5 + FreeAPS/Sources/Models/Configs.swift | 1 + FreeAPS/Sources/Models/Dynamic structs.swift | 6 +- FreeAPS/Sources/Models/FreeAPSSettings.swift | 5 + FreeAPS/Sources/Models/Suggestion.swift | 4 +- .../View/AlternativeBolusCalcRootView.swift | 1 + .../Bolus/View/DefaultBolusCalcRootView.swift | 36 +++- .../View/BolusCalculatorConfigRootView.swift | 2 +- .../DataTable/DataTableStateModel.swift | 2 + .../DataTable/View/DataTableRootView.swift | 13 ++ .../Sources/Modules/Home/HomeStateModel.swift | 3 + .../Home/View/Chart/MainChartView.swift | 10 +- .../Home/View/Header/CurrentGlucoseView.swift | 2 +- .../Modules/Home/View/HomeRootView.swift | 203 ++++++++++++------ .../Home/View/Previews/ActiveCOBView.swift | 2 +- .../Home/View/Previews/ActiveIOBView.swift | 5 +- .../Home/View/Previews/LoopsView.swift | 10 +- .../Modules/Home/View/Previews/TIRView.swift | 134 +++++++++--- .../Sharing/View/SharingRootView.swift | 22 +- .../StatConfig/StatConfigStateModel.swift | 2 + .../StatConfig/View/StatConfigRootView.swift | 1 + FreeAPS/Sources/Shortcuts/AppShortcuts.swift | 7 + .../Carbs/CarbPresetIntentRequest.swift | 2 +- .../Shortcuts/Meals/MealsShortcuts.swift | 158 ++++++++++++++ FreeAPS/Sources/Views/ViewModifiers.swift | 31 ++- 57 files changed, 856 insertions(+), 187 deletions(-) create mode 100644 FreeAPS/Sources/Helpers/ScrollOffset.swift create mode 100644 FreeAPS/Sources/Shortcuts/Meals/MealsShortcuts.swift diff --git a/Config.xcconfig b/Config.xcconfig index a08e6f3fbe..75a089bb5a 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -1,5 +1,5 @@ APP_DISPLAY_NAME = iAPS -APP_VERSION = 4.4.1 +APP_VERSION = 4.6.0 APP_BUILD_NUMBER = 1 COPYRIGHT_NOTICE = DEVELOPER_TEAM = ##TEAM_ID## diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index e7e95e5436..fb4b111bbe 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 192424CB2B7A64E70063CBF0 /* NIghtscoutExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192424CA2B7A64E70063CBF0 /* NIghtscoutExercise.swift */; }; 1924F72C2BA35AE5006644EE /* TotalDailyDose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1924F72B2BA35AE5006644EE /* TotalDailyDose.swift */; }; 1927C8E62744606D00347C69 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1927C8E82744606D00347C69 /* InfoPlist.strings */; }; + 192A9A122C0F5361000BBC29 /* MealsShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192A9A112C0F5361000BBC29 /* MealsShortcuts.swift */; }; 1935364028496F7D001E0B16 /* Dynamic structs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* Dynamic structs.swift */; }; 193F6CDD2A512C8F001240FD /* Loops.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193F6CDC2A512C8F001240FD /* Loops.swift */; }; 194297512B815938006B8A0B /* OverridesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194297502B815938006B8A0B /* OverridesView.swift */; }; @@ -51,6 +52,8 @@ 19A910382A24EF3200C8951B /* ChartsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A910372A24EF3200C8951B /* ChartsView.swift */; }; 19AEF4322B1F5A98006FFE8B /* TIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19AEF4312B1F5A98006FFE8B /* TIRView.swift */; }; 19B0EF2128F6D66200069496 /* Statistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B0EF2028F6D66200069496 /* Statistics.swift */; }; + 19C14F442C29807C009A7E07 /* ScrollOffset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C14F432C29807C009A7E07 /* ScrollOffset.swift */; }; + 19C3FB742C3878BE007AB7E6 /* AppShortcuts.strings in Resources */ = {isa = PBXBuildFile; fileRef = 19C3FB462C3878BE007AB7E6 /* AppShortcuts.strings */; }; 19D466A329AA2B80004D5F33 /* FPUConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A229AA2B80004D5F33 /* FPUConfigDataFlow.swift */; }; 19D466A529AA2BD4004D5F33 /* FPUConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */; }; 19D466A729AA2C22004D5F33 /* FPUConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A629AA2C22004D5F33 /* FPUConfigStateModel.swift */; }; @@ -595,29 +598,7 @@ 1927C8FA2744612500347C69 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; 1927C8FB2744612600347C69 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; 1927C8FE274489BA00347C69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; - 192E77322B9F40C7004F4822 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77462B9F4CF1004F4822 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77472B9F4CF3004F4822 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77482B9F4CF6004F4822 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/AppShortcuts.strings"; sourceTree = ""; }; - 192E77492B9F4CF9004F4822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E774A2B9F4CFB004F4822 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E774B2B9F4CFE004F4822 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E774C2B9F4D01004F4822 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E774D2B9F4D04004F4822 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E774E2B9F4D07004F4822 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E774F2B9F4D08004F4822 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77502B9F4D0A004F4822 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77512B9F4D0B004F4822 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77522B9F4D0D004F4822 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77532B9F4D0E004F4822 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/AppShortcuts.strings"; sourceTree = ""; }; - 192E77542B9F4D0F004F4822 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/AppShortcuts.strings"; sourceTree = ""; }; - 192E77552B9F4D11004F4822 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77562B9F4D12004F4822 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77572B9F4D14004F4822 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77582B9F4D1A004F4822 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E77592B9F4D1B004F4822 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E775A2B9F4D1C004F4822 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/AppShortcuts.strings; sourceTree = ""; }; - 192E775B2B9F4D1E004F4822 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192A9A112C0F5361000BBC29 /* MealsShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealsShortcuts.swift; sourceTree = ""; }; 1935363F28496F7D001E0B16 /* Dynamic structs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dynamic structs.swift"; sourceTree = ""; }; 193F1E392B44C13B00525770 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; 193F1E3A2B44C13B00525770 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; @@ -664,8 +645,32 @@ 19A910372A24EF3200C8951B /* ChartsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartsView.swift; sourceTree = ""; }; 19AEF4312B1F5A98006FFE8B /* TIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TIRView.swift; sourceTree = ""; }; 19B0EF2028F6D66200069496 /* Statistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistics.swift; sourceTree = ""; }; + 19C14F432C29807C009A7E07 /* ScrollOffset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollOffset.swift; sourceTree = ""; }; 19C166682756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = ""; }; 19C166692756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; + 19C3FB452C3878BE007AB7E6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB8B2C3878D9007AB7E6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB8C2C3878E0007AB7E6 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB8D2C3878E2007AB7E6 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB8E2C3878E6007AB7E6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/AppShortcuts.strings"; sourceTree = ""; }; + 19C3FB8F2C3878EA007AB7E6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB902C3878EC007AB7E6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB912C3878EF007AB7E6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB922C3878F2007AB7E6 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB932C3878F5007AB7E6 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB942C3878F7007AB7E6 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB952C3878FA007AB7E6 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB962C3878FC007AB7E6 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB972C3878FE007AB7E6 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB982C387901007AB7E6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/AppShortcuts.strings"; sourceTree = ""; }; + 19C3FB992C387903007AB7E6 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/AppShortcuts.strings"; sourceTree = ""; }; + 19C3FB9A2C387906007AB7E6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB9B2C387908007AB7E6 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB9C2C38790A007AB7E6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB9D2C38790D007AB7E6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB9E2C387910007AB7E6 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FB9F2C387912007AB7E6 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/AppShortcuts.strings; sourceTree = ""; }; + 19C3FBA02C387915007AB7E6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/AppShortcuts.strings; sourceTree = ""; }; 19D466A229AA2B80004D5F33 /* FPUConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigDataFlow.swift; sourceTree = ""; }; 19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigProvider.swift; sourceTree = ""; }; 19D466A629AA2C22004D5F33 /* FPUConfigStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigStateModel.swift; sourceTree = ""; }; @@ -1191,7 +1196,6 @@ 191D529D2BAB4982003F52E1 /* Shortcuts */ = { isa = PBXGroup; children = ( - 192E77332B9F40C7004F4822 /* AppShortcuts.strings */, ); path = Shortcuts; sourceTree = ""; @@ -1204,12 +1208,12 @@ path = Bolus; sourceTree = ""; }; - 1922028F2BAB561600B95BE8 /* InfoPlists */ = { + 192A9A102C0F5284000BBC29 /* Meals */ = { isa = PBXGroup; children = ( - 1927C8E82744606D00347C69 /* InfoPlist.strings */, + 192A9A112C0F5361000BBC29 /* MealsShortcuts.swift */, ); - path = InfoPlists; + path = Meals; sourceTree = ""; }; 192F0FF5276AC36D0085BE4D /* Recovered References */ = { @@ -1269,6 +1273,7 @@ 198377CF266BFEDE004DE65E /* Localizations */ = { isa = PBXGroup; children = ( + 19C3FB732C3878BE007AB7E6 /* Shortcuts */, 196F58CD2BDCFF80009CBF1B /* InfoPlists */, 191D529D2BAB4982003F52E1 /* Shortcuts */, 19D440A926B6FEBD008DA6C8 /* Main */, @@ -1276,6 +1281,14 @@ path = Localizations; sourceTree = ""; }; + 19C3FB732C3878BE007AB7E6 /* Shortcuts */ = { + isa = PBXGroup; + children = ( + 19C3FB462C3878BE007AB7E6 /* AppShortcuts.strings */, + ); + path = Shortcuts; + sourceTree = ""; + }; 19D440A926B6FEBD008DA6C8 /* Main */ = { isa = PBXGroup; children = ( @@ -1918,6 +1931,7 @@ FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */, FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */, CEA4F62229BE10F70011ADF7 /* SavitzkyGolayFilter.swift */, + 19C14F432C29807C009A7E07 /* ScrollOffset.swift */, ); path = Helpers; sourceTree = ""; @@ -2409,6 +2423,7 @@ 196579A52B924BD400642E8E /* Overrides */, 1920BF5B2B9DF4B900E861FE /* Bolus */, CE7CA3452A064973004BE681 /* TempPresets */, + 192A9A102C0F5284000BBC29 /* Meals */, CE7CA34B2A064973004BE681 /* State */, ); path = Shortcuts; @@ -2818,6 +2833,7 @@ 388E597225AD9CF10019842D /* json in Resources */, 38DF178E27733E6800B3528F /* Assets.xcassets in Resources */, 19DA48E829CD339B00EEA1E7 /* Assets.xcassets in Resources */, + 19C3FB742C3878BE007AB7E6 /* AppShortcuts.strings in Resources */, 388E596F25AD96040019842D /* javascript in Resources */, B9CAAEFC2AE70836000F68BC /* branch.txt in Resources */, 1927C8E62744606D00347C69 /* InfoPlist.strings in Resources */, @@ -3035,6 +3051,7 @@ CEB434E528B8FF5D00B70274 /* UIColor.swift in Sources */, 190EBCCB29FF13CB00BA767D /* StatConfigRootView.swift in Sources */, 3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */, + 19C14F442C29807C009A7E07 /* ScrollOffset.swift in Sources */, CE7950242997D81700FA576E /* CGMSettingsView.swift in Sources */, 38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */, 38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */, @@ -3241,6 +3258,7 @@ CE94598729E9E4110047C9C6 /* WatchConfigRootView.swift in Sources */, 903D18976088B09110BCBE29 /* LibreConfigStateModel.swift in Sources */, 9050F378F0063C064D7FFC86 /* LibreConfigRootView.swift in Sources */, + 192A9A122C0F5361000BBC29 /* MealsShortcuts.swift in Sources */, B7C465E9472624D8A2BE2A6A /* CalibrationsDataFlow.swift in Sources */, 320D030F724170A637F06D50 /* CalibrationsProvider.swift in Sources */, 19E1F7E829D082D0005C8D20 /* IconConfigDataFlow.swift in Sources */, @@ -3327,36 +3345,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 191D52A32BAB5397003F52E1 /* AppShortcuts.strings */ = { - isa = PBXVariantGroup; - children = ( - 191D52A42BAB5397003F52E1 /* de */, - 191D52A52BAB5397003F52E1 /* he */, - 191D52A62BAB5397003F52E1 /* ar */, - 191D52A72BAB5397003F52E1 /* zh-Hans */, - 191D52A82BAB5397003F52E1 /* en */, - 191D52A92BAB5397003F52E1 /* uk */, - 191D52AA2BAB5397003F52E1 /* nb */, - 191D52AB2BAB5397003F52E1 /* es */, - 191D52AC2BAB5397003F52E1 /* da */, - 191D52AD2BAB5397003F52E1 /* it */, - 191D52AE2BAB5397003F52E1 /* sk */, - 191D52AF2BAB5397003F52E1 /* sv */, - 191D52B02BAB5397003F52E1 /* hu */, - 191D52B12BAB5397003F52E1 /* tr */, - 191D52B22BAB5397003F52E1 /* pl */, - 191D52B32BAB5397003F52E1 /* pt-BR */, - 191D52B42BAB5397003F52E1 /* vi */, - 191D52B52BAB5397003F52E1 /* ru */, - 191D52B62BAB5397003F52E1 /* fr */, - 191D52B72BAB5397003F52E1 /* fi */, - 191D52B82BAB5397003F52E1 /* nl */, - 191D52B92BAB5397003F52E1 /* pt-PT */, - 191D52BA2BAB5397003F52E1 /* ca */, - ); - name = AppShortcuts.strings; - sourceTree = ""; - }; 1927C8E82744606D00347C69 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -3418,6 +3406,36 @@ name = Localizable.strings; sourceTree = ""; }; + 19C3FB462C3878BE007AB7E6 /* AppShortcuts.strings */ = { + isa = PBXVariantGroup; + children = ( + 19C3FB452C3878BE007AB7E6 /* de */, + 19C3FB8B2C3878D9007AB7E6 /* en */, + 19C3FB8C2C3878E0007AB7E6 /* ar */, + 19C3FB8D2C3878E2007AB7E6 /* ca */, + 19C3FB8E2C3878E6007AB7E6 /* zh-Hans */, + 19C3FB8F2C3878EA007AB7E6 /* da */, + 19C3FB902C3878EC007AB7E6 /* nl */, + 19C3FB912C3878EF007AB7E6 /* fi */, + 19C3FB922C3878F2007AB7E6 /* fr */, + 19C3FB932C3878F5007AB7E6 /* he */, + 19C3FB942C3878F7007AB7E6 /* hu */, + 19C3FB952C3878FA007AB7E6 /* it */, + 19C3FB962C3878FC007AB7E6 /* nb */, + 19C3FB972C3878FE007AB7E6 /* pl */, + 19C3FB982C387901007AB7E6 /* pt-BR */, + 19C3FB992C387903007AB7E6 /* pt-PT */, + 19C3FB9A2C387906007AB7E6 /* ru */, + 19C3FB9B2C387908007AB7E6 /* sk */, + 19C3FB9C2C38790A007AB7E6 /* es */, + 19C3FB9D2C38790D007AB7E6 /* sv */, + 19C3FB9E2C387910007AB7E6 /* tr */, + 19C3FB9F2C387912007AB7E6 /* uk */, + 19C3FBA02C387915007AB7E6 /* vi */, + ); + name = AppShortcuts.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/FreeAPS/Resources/javascript/prepare/determine-basal.js b/FreeAPS/Resources/javascript/prepare/determine-basal.js index 66722ba0c4..6c4fc4663c 100644 --- a/FreeAPS/Resources/javascript/prepare/determine-basal.js +++ b/FreeAPS/Resources/javascript/prepare/determine-basal.js @@ -87,6 +87,11 @@ function generate(iob, currenttemp, glucose, profile, autosens = null, meal = nu } var glucose_status = freeaps_glucoseGetLast(glucose); + // In case Basal Rate been set in midleware + if (profile.set_basal && profile.basal_rate) { + console.log("Basal Rate set by middleware to " + profile.basal_rate + " U/h."); + } + return freeaps_determineBasal(glucose_status, currenttemp, iob, profile, autosens_data, meal_data, freeaps_basalSetTemp, microbolusAllowed, reservoir_data, clock); } diff --git a/FreeAPS/Resources/javascript/prepare/profile.js b/FreeAPS/Resources/javascript/prepare/profile.js index 5d67701e91..572792dc0b 100644 --- a/FreeAPS/Resources/javascript/prepare/profile.js +++ b/FreeAPS/Resources/javascript/prepare/profile.js @@ -82,6 +82,8 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data } var tdd_factor = { }; + var set_basal = false; + var basal_rate = { }; var inputs = { }; //add all preferences to the inputs @@ -108,6 +110,8 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data inputs.model = model_data; inputs.autotune = autotune_data; inputs.tddFactor = tdd_factor; + inputs.set_basal = set_basal; + inputs.basal_rate = basal_rate; if (autotune_data) { if (autotune_data.basalprofile) { inputs.basals = autotune_data.basalprofile; } diff --git a/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift b/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift index 40ed4b17cf..3747862418 100644 --- a/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift +++ b/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift @@ -94,7 +94,11 @@ final class OpenAPS { // Update Suggestion if var suggestion = Suggestion(from: suggested) { - // Add some reasons + // Process any eventual middleware basal rate + if let newSuggestion = self.overrideBasal(alteredProfile: alteredProfile, oref0Suggestion: suggestion) { + suggestion = newSuggestion + } + // Add some reasons, when needed suggestion.reason = self.reasons( reason: suggestion.reason, suggestion: suggestion, @@ -304,7 +308,7 @@ final class OpenAPS { } } - // Dsiplay either Target or Override (where target is included). + // Display either Target or Override (where target is included). let targetGlucose = suggestion.targetBG if targetGlucose != nil, let or = OverrideStorage().fetchLatestOverride().first, or.enabled { var orString = ", Override:" @@ -372,6 +376,23 @@ final class OpenAPS { return reasonString } + private func overrideBasal(alteredProfile: RawJSON, oref0Suggestion: Suggestion) -> Suggestion? { + guard let changeRate = readJSON(json: alteredProfile, variable: "set_basal"), Bool(changeRate) ?? false, + let basal_rate_is = readJSON(json: alteredProfile, variable: "basal_rate") else { return nil } + + var returnSuggestion = oref0Suggestion + let basal_rate = Decimal(string: basal_rate_is) ?? 0 + returnSuggestion.rate = basal_rate + returnSuggestion.duration = 30 + var reasonString = oref0Suggestion.reason + let endIndex = reasonString.endIndex + let insertedResons: String = reasonString + "\n\nBasal Rate overridden in middleware to: \(basal_rate) U/h" + reasonString.insert(contentsOf: insertedResons, at: endIndex) + returnSuggestion.reason = reasonString + + return returnSuggestion + } + private func readJSON(json: RawJSON, variable: String) -> String? { if let string = json.debugDescription.components(separatedBy: ",").filter({ $0.contains(variable) }).first { let targetComponents = string.components(separatedBy: ":") @@ -432,12 +453,13 @@ final class OpenAPS { let disableCGMError = settingsData?.disableCGMError ?? true let cd = CoreDataStorage() + let os = OverrideStorage() // TDD let uniqueEvents = cd.fetchTDD(interval: DateFilter().tenDays) // Temp Targets using slider let sliderArray = cd.fetchTempTargetsSlider() // Overrides - let overrideArray = OverrideStorage().fetchNumberOfOverrides(numbers: 2) + let overrideArray = os.fetchNumberOfOverrides(numbers: 2) // Temp Target let tempTargetsArray = cd.fetchTempTargets() @@ -474,6 +496,11 @@ final class OpenAPS { let overrideMaxIOB = overrideArray.first?.overrideMaxIOB ?? false let maxIOB = overrideArray.first?.maxIOB ?? (preferences?.maxIOB ?? 0) as NSDecimalNumber + var name = "" + if useOverride, overrideArray.first?.isPreset ?? false, let overridePreset = os.isPresetName() { + name = overridePreset + } + if nrOfIndeces == 0 { nrOfIndeces = 1 } @@ -553,7 +580,8 @@ final class OpenAPS { uamMinutes: (overrideArray.first?.uamMinutes ?? uamMinutes) as Decimal, maxIOB: maxIOB as Decimal, overrideMaxIOB: overrideMaxIOB, - disableCGMError: disableCGMError + disableCGMError: disableCGMError, + preset: name ) storage.save(averages, as: OpenAPS.Monitor.dynamicVariables) return self.loadFileFromStorage(name: Monitor.dynamicVariables) diff --git a/FreeAPS/Sources/APS/OpenAPS/TotalDailyDose.swift b/FreeAPS/Sources/APS/OpenAPS/TotalDailyDose.swift index 53c2c57f80..72ca7b0eb6 100644 --- a/FreeAPS/Sources/APS/OpenAPS/TotalDailyDose.swift +++ b/FreeAPS/Sources/APS/OpenAPS/TotalDailyDose.swift @@ -47,6 +47,12 @@ final class TotalDailyDose { } else { return 0 } } + func insulinToday(_ data: [PumpHistoryEvent], increment: Double) -> (bolus: Decimal, basal: Decimal, hours: Double) { + let filtered = data.filter({ $0.timestamp > Calendar.current.startOfDay(for: Date()) }) + + return totalDailyDose(filtered, increment: increment) + } + // All delivered boli (manual, external and SMBs) private func bolus(_ data: [PumpHistoryEvent]) -> Decimal { data.compactMap(\.amount).reduce(0, +) diff --git a/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift b/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift index 5484e25c97..388c72c038 100644 --- a/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift +++ b/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift @@ -176,4 +176,34 @@ final class CoreDataStorage { } return nr.first } + + func fetchMealPreset(_ name: String) -> Presets? { + var presetsArray = [Presets]() + var preset: Presets? + coredataContext.performAndWait { + let requestPresets = Presets.fetchRequest() as NSFetchRequest + requestPresets.predicate = NSPredicate( + format: "dish == %@", name + ) + try? presetsArray = self.coredataContext.fetch(requestPresets) + + guard let mealPreset = presetsArray.first else { + return + } + preset = mealPreset + } + return preset + } + + func fetchMealPresets() -> [Presets] { + var presetsArray = [Presets]() + coredataContext.performAndWait { + let requestPresets = Presets.fetchRequest() as NSFetchRequest + requestPresets.predicate = NSPredicate( + format: "dish != %@", "" as String + ) + try? presetsArray = self.coredataContext.fetch(requestPresets) + } + return presetsArray + } } diff --git a/FreeAPS/Sources/Helpers/ScrollOffset.swift b/FreeAPS/Sources/Helpers/ScrollOffset.swift new file mode 100644 index 0000000000..37045b13e2 --- /dev/null +++ b/FreeAPS/Sources/Helpers/ScrollOffset.swift @@ -0,0 +1,8 @@ +import SwiftUI + +struct ScrollViewOffsetPreferenceKey: PreferenceKey { + static var defaultValue = CGFloat.zero + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value += nextValue() + } +} diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index 76a8b5728a..4303cfb5ae 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -1049,6 +1049,18 @@ Enact a temp Basal or a temp target */ /* Override Shortcut */ "Activates an %@ Override Preset" = "Activates an %@ Override Preset"; +/* Meal Prestes Shortcut */ +"Which meal preset would you like to use?" = "Which meal preset would you like to use?"; + +/* Meal Prestes Shortcut */ +"Are you sure you want to use the meal preset %@?" = "Are you sure you want to use the meal preset %@?"; + +/* Meal Prestes Shortcut */ +"The Meal" = "The Meal"; + +/* Meal Prestes Shortcut */ +"has been added to iAPS" = "has been added to iAPS"; + /* */ "Schedule " = "Schedule "; @@ -2494,6 +2506,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings index ac6af717d3..04cf5a4453 100644 --- a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings @@ -1049,6 +1049,18 @@ Enact a temp Basal or a temp target */ /* Override Shortcut */ "Activates an %@ Override Preset" = "Aktiverar en %@ - profil"; +/* Meal Prestes Shortcut */ +"Which meal preset would you like to use?" = "Vilken måltid vill du lägga till?"; + +/* Meal Prestes Shortcut */ +"Are you sure you want to use the meal preset %@?" = "Är du säker att du vill lägga till måltiden %@?"; + +/* Meal Prestes Shortcut */ +"The Meal" = "Måltiden"; + +/* Meal Prestes Shortcut */ +"has been added to iAPS" = "har lagt till i iAPS"; + /* */ "Schedule " = "Schema "; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/ar.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/ar.lproj/AppShortcuts.strings index 91ed585d9d..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/ar.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/ar.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/ca.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/ca.lproj/AppShortcuts.strings index 91ed585d9d..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/ca.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/ca.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/da.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/da.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/da.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/da.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/de.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/de.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/de.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/de.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/en.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/en.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/en.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/en.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/es.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/es.lproj/AppShortcuts.strings index 91ed585d9d..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/es.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/es.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/fi.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/fi.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/fi.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/fi.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/fr.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/fr.lproj/AppShortcuts.strings index 91ed585d9d..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/fr.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/fr.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/he.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/he.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/he.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/he.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/hu.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/hu.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/hu.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/hu.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/it.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/it.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/it.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/it.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/nb.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/nb.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/nb.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/nb.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/nl.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/nl.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/nl.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/nl.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/pl.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/pl.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/pl.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/pl.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/pt-BR.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/pt-BR.lproj/AppShortcuts.strings index 91ed585d9d..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/pt-BR.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/pt-BR.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/pt-PT.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/pt-PT.lproj/AppShortcuts.strings index 91ed585d9d..e95210725c 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/pt-PT.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/pt-PT.lproj/AppShortcuts.strings @@ -33,3 +33,15 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/ru.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/ru.lproj/AppShortcuts.strings index 91ed585d9d..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/ru.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/ru.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/sk.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/sk.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/sk.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/sk.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/sv.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/sv.lproj/AppShortcuts.strings index 306cdce8ce..4c98aff361 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/sv.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/sv.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "Ger bolus med ${applicationName}"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Sparade måltider"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/tr.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/tr.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/tr.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/tr.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/uk.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/uk.lproj/AppShortcuts.strings index 91ed585d9d..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/uk.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/uk.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/vi.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/vi.lproj/AppShortcuts.strings index 91ed585d9d..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/vi.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/vi.lproj/AppShortcuts.strings @@ -33,3 +33,9 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; + +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Localizations/Shortcuts/zh-Hans.lproj/AppShortcuts.strings b/FreeAPS/Sources/Localizations/Shortcuts/zh-Hans.lproj/AppShortcuts.strings index c5afbd5f54..07f7e4a438 100644 --- a/FreeAPS/Sources/Localizations/Shortcuts/zh-Hans.lproj/AppShortcuts.strings +++ b/FreeAPS/Sources/Localizations/Shortcuts/zh-Hans.lproj/AppShortcuts.strings @@ -34,3 +34,8 @@ /* iAPS Bolus shortcut description */ "${applicationName} Enacts a bolus" = "${applicationName} Enacts a bolus"; +/* iAPS Override shortcut */ +"${applicationName} Meal Presets" = "${applicationName} Meal Presets"; + +/* iAPS Meal presets shortcut description */ +"Uses an ${applicationName} Meal Preset" = "Uses an ${applicationName} Meal Preset"; diff --git a/FreeAPS/Sources/Models/Configs.swift b/FreeAPS/Sources/Models/Configs.swift index c11fe25dbb..e9c0760965 100644 --- a/FreeAPS/Sources/Models/Configs.swift +++ b/FreeAPS/Sources/Models/Configs.swift @@ -31,6 +31,7 @@ public enum IAPSconfig { extension Font { static let buttonFont = Font.custom("TimeButtonFont", fixedSize: 14) // Same as Eventual BG size + static let infoSymbolFont = Font.custom("TimeButtonFont", fixedSize: 16) // Same as Eventual BG size static let loopFont = Font.custom("LoopFont", size: 13) // Loop min ago static let statusFont = Font.custom("StatusFont", size: 16) // IOB, COB etc. diff --git a/FreeAPS/Sources/Models/Dynamic structs.swift b/FreeAPS/Sources/Models/Dynamic structs.swift index d66a369cd8..01e585d4f9 100644 --- a/FreeAPS/Sources/Models/Dynamic structs.swift +++ b/FreeAPS/Sources/Models/Dynamic structs.swift @@ -27,6 +27,7 @@ struct DynamicVariables: JSON, Codable { var maxIOB: Decimal var overrideMaxIOB: Bool var disableCGMError: Bool + var preset: String init( average_total_data: Decimal, @@ -54,7 +55,8 @@ struct DynamicVariables: JSON, Codable { uamMinutes: Decimal, maxIOB: Decimal, overrideMaxIOB: Bool, - disableCGMError: Bool + disableCGMError: Bool, + preset: String ) { self.average_total_data = average_total_data self.weightedAverage = weightedAverage @@ -82,6 +84,7 @@ struct DynamicVariables: JSON, Codable { self.maxIOB = maxIOB self.overrideMaxIOB = overrideMaxIOB self.disableCGMError = disableCGMError + self.preset = preset } } @@ -113,6 +116,7 @@ extension DynamicVariables { case maxIOB case overrideMaxIOB case disableCGMError + case preset } } diff --git a/FreeAPS/Sources/Models/FreeAPSSettings.swift b/FreeAPS/Sources/Models/FreeAPSSettings.swift index d974f8274a..b722f1f908 100644 --- a/FreeAPS/Sources/Models/FreeAPSSettings.swift +++ b/FreeAPS/Sources/Models/FreeAPSSettings.swift @@ -64,6 +64,7 @@ struct FreeAPSSettings: JSON, Equatable { var useInsulinBars: Bool = false var disableCGMError: Bool = true var uploadVersion: Bool = true + var skipGlucoseChart: Bool = false var birthDate = Date.distantPast // var sex: Sex = .secret var sexSetting: Int = 3 @@ -332,6 +333,10 @@ extension FreeAPSSettings: Decodable { settings.uploadVersion = uploadVersion } + if let skipGlucoseChart = try? container.decode(Bool.self, forKey: .skipGlucoseChart) { + settings.skipGlucoseChart = skipGlucoseChart + } + if let birthDate = try? container.decode(Date.self, forKey: .birthDate) { settings.birthDate = birthDate } diff --git a/FreeAPS/Sources/Models/Suggestion.swift b/FreeAPS/Sources/Models/Suggestion.swift index 9e2632e909..3cebb8a7b2 100644 --- a/FreeAPS/Sources/Models/Suggestion.swift +++ b/FreeAPS/Sources/Models/Suggestion.swift @@ -6,8 +6,8 @@ struct Suggestion: JSON, Equatable { let insulinReq: Decimal? let eventualBG: Int? let sensitivityRatio: Decimal? - let rate: Decimal? - let duration: Int? + var rate: Decimal? + var duration: Int? let iob: Decimal? let cob: Decimal? var predictions: Predictions? diff --git a/FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift b/FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift index 0fd90a5e71..017338cd15 100644 --- a/FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift +++ b/FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift @@ -80,6 +80,7 @@ extension Bolus { Image(systemName: "info.bubble") .symbolRenderingMode(.palette) .foregroundStyle(colorScheme == .light ? .black : .white, .blue) + .font(.infoSymbolFont) Text("Calculations") }) .foregroundStyle(.blue) diff --git a/FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift b/FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift index 6133d5844c..b75ecca43c 100644 --- a/FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift +++ b/FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift @@ -64,7 +64,7 @@ extension Bolus { if fetch { Section { mealEntries.asAny() - } // header: { Text("Meal Summary") } + } } Section { @@ -76,19 +76,39 @@ extension Bolus { } } else { HStack { - Text("Insulin recommended") - Image(systemName: "info.bubble") - .symbolRenderingMode(.palette) - .foregroundStyle(.primary, .blue) - .onTapGesture { - presentInfo.toggle() + Button(action: { + presentInfo.toggle() + }, label: { + Image(systemName: "info.bubble") + .symbolRenderingMode(.palette) + .foregroundStyle(colorScheme == .light ? .black : .white, .blue) + .font(.infoSymbolFont) + Text("Calculations") + }) + .foregroundStyle(.blue) + .font(.footnote) + .buttonStyle(PlainButtonStyle()) + .frame(maxWidth: .infinity, alignment: .leading) + if state.fattyMeals { + Spacer() + Toggle(isOn: $state.useFattyMealCorrectionFactor) { + Text("Fatty Meal") + } + .toggleStyle(CheckboxToggleStyle()) + .font(.footnote) + .onChange(of: state.useFattyMealCorrectionFactor) { _ in + state.insulinCalculated = state.calculateInsulin() } + } + } + HStack { + Text("Insulin recommended") Spacer() Text( formatter - .string(from: state.insulinCalculated as NSNumber)! + + .string(from: state.insulinCalculated as NSNumber) ?? "" + NSLocalizedString(" U", comment: "Insulin unit") ).foregroundColor(.secondary) .onTapGesture { diff --git a/FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift b/FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift index ef68ebc586..6e4d126b02 100644 --- a/FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift +++ b/FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift @@ -37,7 +37,7 @@ extension BolusCalculatorConfig { Text( state .useCalc ? - "Depending on your settings the Swift bolus calculator is using data from the OpenAPS glucose predictions and/or from IOB, COB, glucose trend, current gluccose and target glucose. At the end of the calculation a custom factor is applied (default 0.8).\n\nWhen Eventual glucose prediction is disabled you can also have the option in your bolus calculator to apply another (!) customizable factor at the end of the calculation which could be useful for fatty meals, e.g Pizza (default 0.7).\n\nThe bolus calculator is NOT using the OpenAPS variable \"insulinRequired\", made for SMBs." : + "Depending on your settings the Swift bolus calculator is using data from the OpenAPS glucose predictions and/or from IOB, COB, glucose trend, current gluccose and target glucose. At the end of the calculation a custom factor is applied (default 0.8).\n\nYou can also have the option in your bolus calculator to apply another (!) customizable factor at the end of the calculation which could be useful for fatty meals, e.g Pizza (default 0.7).\n\nThe bolus calculator is NOT using the OpenAPS variable \"insulinRequired\", made for SMBs." : "" ) } diff --git a/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift b/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift index cdf3169d1b..f3b3138026 100644 --- a/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift +++ b/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift @@ -19,6 +19,7 @@ extension DataTable { @Published var externalInsulinAmount: Decimal = 0 @Published var externalInsulinDate = Date() @Published var tdd: (Decimal, Decimal, Double) = (0, 0, 0) + @Published var insulinToday: (Decimal, Decimal, Double) = (0, 0, 0) @Published var basalInsulin: Decimal = 0 var units: GlucoseUnits = .mmolL @@ -150,6 +151,7 @@ extension DataTable { DispatchQueue.main.async { let increments = self.settingsManager.preferences.bolusIncrement self.tdd = TotalDailyDose().totalDailyDose(self.provider.pumpHistory(), increment: Double(increments)) + self.insulinToday = TotalDailyDose().insulinToday(self.provider.pumpHistory(), increment: Double(increments)) } } } diff --git a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift index ef57b248f1..42c02db3ad 100644 --- a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift +++ b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift @@ -134,6 +134,19 @@ extension DataTable { } }.foregroundStyle(.gray) + HStack { + HStack { + Text("Today") + Text(insulinFormatter.string(from: (state.insulinToday.0 + state.tdd.1) as NSNumber) ?? "") + Text("U") + } + Spacer() + HStack { + Text(hourFormatter.string(from: state.insulinToday.2 as NSNumber) ?? "") + Text("h") + } + }.foregroundStyle(.gray) + if !state.treatments.isEmpty { if !showFutureEntries { ForEach(state.treatments.filter { item in diff --git a/FreeAPS/Sources/Modules/Home/HomeStateModel.swift b/FreeAPS/Sources/Modules/Home/HomeStateModel.swift index 1cee3314f2..adb27961c9 100644 --- a/FreeAPS/Sources/Modules/Home/HomeStateModel.swift +++ b/FreeAPS/Sources/Modules/Home/HomeStateModel.swift @@ -88,6 +88,7 @@ extension Home { @Published var tdd2DaysAgo: Decimal = 0 @Published var tdd3DaysAgo: Decimal = 0 @Published var tddActualAverage: Decimal = 0 + @Published var skipGlucoseChart: Bool = false let coredataContext = CoreDataStack.shared.persistentContainer.viewContext @@ -139,6 +140,7 @@ extension Home { minimumSMB = settingsManager.settings.minimumSMB maxBolus = settingsManager.pumpSettings.maxBolus useInsulinBars = settingsManager.settings.useInsulinBars + skipGlucoseChart = settingsManager.settings.skipGlucoseChart broadcaster.register(GlucoseObserver.self, observer: self) broadcaster.register(SuggestionObserver.self, observer: self) @@ -589,6 +591,7 @@ extension Home.StateModel: minimumSMB = settingsManager.settings.minimumSMB maxBolus = settingsManager.pumpSettings.maxBolus useInsulinBars = settingsManager.settings.useInsulinBars + skipGlucoseChart = settingsManager.settings.skipGlucoseChart setupGlucose() setupOverrideHistory() setupData() diff --git a/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift b/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift index e4efacb514..5b7ff29544 100644 --- a/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift @@ -119,7 +119,7 @@ struct MainChartView: View { @State private var glucoseYRange: GlucoseYRange = (0, 0, 0, 0) @State private var offset: CGFloat = 0 @State private var cachedMaxBasalRate: Decimal? - @State private var legends: Bool = false + @State private var legends: Bool = true private let calculationQueue = DispatchQueue(label: "MainChartView.calculationQueue") @@ -203,9 +203,9 @@ struct MainChartView: View { update(fullSize: geo.size) } } - .onTapGesture { - legends.toggle() - } + /* .onTapGesture { + legends.toggle() + } */ } var legendPanel: some View { @@ -255,7 +255,7 @@ struct MainChartView: View { } } } - .padding(.bottom, 20) + .padding(.bottom, 8) } } diff --git a/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift b/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift index 01f74ae702..2208217a69 100644 --- a/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift @@ -96,7 +96,7 @@ struct CurrentGlucoseView: View { }.frame(maxWidth: .infinity, alignment: .center) .offset(x: offset, y: 0) } - .dynamicTypeSize(DynamicTypeSize.medium ... DynamicTypeSize.xxLarge) + .dynamicTypeSize(DynamicTypeSize.medium ... DynamicTypeSize.xLarge) } } } diff --git a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift index 767804d4cb..bc38ca3db4 100644 --- a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift +++ b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift @@ -1,3 +1,4 @@ +import Charts import CoreData import SpriteKit import SwiftDate @@ -13,7 +14,12 @@ extension Home { @State var showCancelAlert = false @State var showCancelTTAlert = false @State var triggerUpdate = false + @State var scrollOffset = CGFloat.zero + @State var display = false + @Namespace var scrollSpace + + let scrollAmount: CGFloat = 290 let buttonFont = Font.custom("TimeButtonFont", size: 14) @Environment(\.managedObjectContext) var moc @@ -221,16 +227,8 @@ extension Home { } var infoPanel: some View { - /* addBackground() - .frame(minHeight: 40, maxHeight: 40) - .overlay { */ info .frame(minHeight: 35, maxHeight: 35) - /* } - .clipShape(RoundedRectangle(cornerRadius: 15)) - .addShadows() - .padding(.horizontal, 10) - // .background(colorScheme == .light ? .gray.opacity(0.1) : .white.opacity(0.05)) */ } var mainChart: some View { @@ -281,20 +279,6 @@ extension Home { let isOverride = fetchedPercent.first?.enabled ?? false let isTarget = (state.tempTarget != nil) HStack { - Button { state.showModal(for: .dataTable) } - label: { - ZStack(alignment: Alignment(horizontal: .leading, vertical: .bottom)) { - Image(systemName: "book") - .symbolRenderingMode(.hierarchical) - .resizable() - .frame( - width: IAPSconfig.buttonSize * 0.9, - height: IAPSconfig.buttonSize - ) - .foregroundColor(.gray) - } - }.buttonStyle(.borderless) - Spacer() Button { state.showModal(for: .addCarbs(editMode: false, override: false)) } label: { ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) { @@ -314,6 +298,31 @@ extension Home { } }.buttonStyle(.borderless) Spacer() + Button { + state.showModal(for: .bolus( + waitForSuggestion: state.useCalc ? true : false, + fetch: false + )) + } + label: { + Image(systemName: "syringe") + .renderingMode(.template) + .font(.custom("Buttons", size: 24)) + } + .buttonStyle(.borderless) + .foregroundColor(.insulin) + Spacer() + if state.allowManualTemp { + Button { state.showModal(for: .manualTempBasal) } + label: { + Image("bolus1") + .renderingMode(.template) + .resizable() + .frame(width: IAPSconfig.buttonSize, height: IAPSconfig.buttonSize, alignment: .bottom) + } + .foregroundColor(.insulin) + Spacer() + } ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) { Image(systemName: isOverride ? "person.fill" : "person") .symbolRenderingMode(.palette) @@ -354,31 +363,6 @@ extension Home { } } Spacer() - Button { - state.showModal(for: .bolus( - waitForSuggestion: state.useCalc ? true : false, - fetch: false - )) - } - label: { - Image(systemName: "syringe") - .renderingMode(.template) - .font(.custom("Buttons", size: 24)) - } - .buttonStyle(.borderless) - .foregroundColor(.insulin) - Spacer() - if state.allowManualTemp { - Button { state.showModal(for: .manualTempBasal) } - label: { - Image("bolus1") - .renderingMode(.template) - .resizable() - .frame(width: IAPSconfig.buttonSize, height: IAPSconfig.buttonSize, alignment: .bottom) - } - .foregroundColor(.insulin) - Spacer() - } Button { state.showModal(for: .settings) } label: { Image(systemName: "gear") @@ -406,20 +390,17 @@ extension Home { } var chart: some View { - addColouredBackground() + let ratio = state.timeSettings ? 1.61 : 1.44 + let ratio2 = state.timeSettings ? 1.65 : 1.51 + + return addColouredBackground() .overlay { VStack(spacing: 0) { infoPanel mainChart - if state.timeSettings { - timeSetting - } } } - .frame( - minHeight: UIScreen.main.bounds - .height / (state.timeSettings ? 1.50 : fontSize < .extraExtraLarge ? 1.46 : 1.49) - ) + .frame(minHeight: UIScreen.main.bounds.height / (fontSize < .extraExtraLarge ? ratio : ratio2)) } var carbsAndInsulinView: some View { @@ -487,7 +468,7 @@ extension Home { var activeIOBView: some View { addBackground() - .frame(minHeight: 500) + .frame(minHeight: 430) .overlay { ActiveIOBView( data: $state.iobData, @@ -507,7 +488,7 @@ extension Home { var activeCOBView: some View { addBackground() - .frame(minHeight: 250) + .frame(minHeight: 230) .overlay { ActiveCOBView(data: $state.iobData) } @@ -596,7 +577,8 @@ extension Home { @ViewBuilder private func headerView(_ geo: GeometryProxy) -> some View { addHeaderBackground() .frame( - maxHeight: fontSize < .extraExtraLarge ? 125 + geo.safeAreaInsets.top : 135 + geo.safeAreaInsets.top + maxHeight: fontSize < .extraExtraLarge ? 125 + geo.safeAreaInsets.top : 135 + geo + .safeAreaInsets.top ) .overlay { VStack { @@ -613,14 +595,74 @@ extension Home { .frame(maxHeight: .infinity, alignment: .bottom) .padding(.bottom, 2) } - .dynamicTypeSize(...DynamicTypeSize.xxLarge) + .dynamicTypeSize(...DynamicTypeSize.xLarge) .padding(.horizontal, 10) } - }.padding(.top, geo.safeAreaInsets.top).padding(.bottom, colorScheme == .dark ? 0 : 10) + }.padding(.top, geo.safeAreaInsets.top).padding(.bottom, 10) + } + .clipShape(Rectangle()) + } + + @ViewBuilder private func glucoseHeaderView() -> some View { + addHeaderBackground() + .frame(maxHeight: 90) + .overlay { + VStack { + ZStack { + glucosePreview.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .dynamicTypeSize(...DynamicTypeSize.medium) + } + } } .clipShape(Rectangle()) } + var glucosePreview: some View { + let data = state.glucose + let minimum = data.compactMap(\.glucose).min() ?? 0 + let minimumRange = Double(minimum) * 0.8 + let maximum = Double(data.compactMap(\.glucose).max() ?? 0) * 1.1 + + let high = state.highGlucose + let low = state.lowGlucose + let veryHigh = 198 + + return Chart(data) { + PointMark( + x: .value("Time", $0.dateString), + y: .value("Glucose", Double($0.glucose ?? 0) * (state.units == .mmolL ? 0.0555 : 1.0)) + ) + .foregroundStyle( + (($0.glucose ?? 0) > veryHigh || Decimal($0.glucose ?? 0) < low) ? Color(.red) : Decimal($0.glucose ?? 0) > + high ? Color(.yellow) : Color(.darkGreen) + ) + .symbolSize(7) + } + .chartXAxis { + AxisMarks(values: .stride(by: .hour, count: 2)) { _ in + AxisValueLabel( + format: .dateTime.hour(.defaultDigits(amPM: .omitted)) + .locale(Locale(identifier: "sv")) + ) + AxisGridLine() + } + } + .chartYAxis { + AxisMarks(values: .automatic(desiredCount: 3)) + } + .chartYScale( + domain: minimumRange * (state.units == .mmolL ? 0.0555 : 1.0) ... maximum * (state.units == .mmolL ? 0.0555 : 1.0) + ) + .chartXScale( + domain: Date.now.addingTimeInterval(-1.days.timeInterval) ... Date.now + ) + .frame(maxHeight: 70) + .padding(.leading, 30) + .padding(.trailing, 32) + .padding(.top, 10) + .padding(.bottom, 10) + } + var timeSetting: some View { let string = "\(state.hours) " + NSLocalizedString("hours", comment: "") + " " return Menu(string) { @@ -631,7 +673,7 @@ extension Home { Button("UI/UX Settings", action: { state.showModal(for: .statisticsConfig) }) } .buttonStyle(.borderless) - .foregroundStyle(.secondary) + .foregroundStyle(colorScheme == .dark ? .primary : Color(.darkGray)) .font(.timeSettingFont) .padding(.vertical, 15) .background(TimeEllipse(characters: string.count)) @@ -639,18 +681,41 @@ extension Home { var body: some View { GeometryReader { geo in - VStack { + VStack(spacing: 0) { headerView(geo) + if !state.skipGlucoseChart, scrollOffset > scrollAmount { + glucoseHeaderView() + .transition(.move(edge: .top)) + } + ScrollView { - chart - preview.padding(.top, 15) - loopPreview.padding(.top, 15) - if state.iobData.count > 5 { - activeCOBView.padding(.top, 15) - activeIOBView.padding(.top, 15) + ScrollViewReader { _ in + LazyVStack { + chart + if state.timeSettings { timeSetting } + preview.padding(.top, state.timeSettings ? 5 : 15) + loopPreview.padding(.top, 15) + if state.iobData.count > 5 { + activeCOBView.padding(.top, 15) + activeIOBView.padding(.top, 15) + } + } + .background(GeometryReader { geo in + let offset = -geo.frame(in: .named(scrollSpace)).minY + Color.clear + .preference( + key: ScrollViewOffsetPreferenceKey.self, + value: offset + ) + }) + } + } + .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in + scrollOffset = value + if !state.skipGlucoseChart, scrollOffset > scrollAmount { + display.toggle() } } - .scrollIndicators(.hidden) buttonPanel(geo) } .background( diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveCOBView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveCOBView.swift index 5e6e2cb22c..db17ddcd4b 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveCOBView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveCOBView.swift @@ -15,7 +15,7 @@ struct ActiveCOBView: View { var body: some View { VStack { Text("Active Carbohydrates").font(.previewHeadline).padding(.top, 20) - cobView().frame(maxHeight: 150).padding(.vertical, 10).padding(.horizontal, 20) + cobView().frame(maxHeight: 130).padding(.vertical, 10).padding(.horizontal, 20) .padding(.bottom, 10) }.dynamicTypeSize(...DynamicTypeSize.medium) } diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift index 75fc459f0f..892d15b6c6 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift @@ -30,9 +30,9 @@ struct ActiveIOBView: View { var body: some View { VStack { Text("Active Insulin").font(.previewHeadline).padding(.top, 20) - iobView().frame(maxHeight: 200).padding(.horizontal, 20) + iobView().frame(maxHeight: 130).padding(.horizontal, 20) sumView().frame(maxHeight: 250).padding(.vertical, 30) - }.dynamicTypeSize(...DynamicTypeSize.medium) + }.dynamicTypeSize(...DynamicTypeSize.xLarge) } @ViewBuilder private func iobView() -> some View { @@ -145,6 +145,7 @@ struct ActiveIOBView: View { } } .padding(.horizontal, 20) + .dynamicTypeSize(...DynamicTypeSize.large) } private func isTDD(_ insulin: Decimal) -> Bool { diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/LoopsView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/LoopsView.swift index 07542bb256..efa6629a4f 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/LoopsView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/LoopsView.swift @@ -34,7 +34,7 @@ struct LoopsView: View { } .padding(.top, 20) .padding(.bottom, 15) - .dynamicTypeSize(...DynamicTypeSize.accessibility1) + .dynamicTypeSize(...DynamicTypeSize.xLarge) } func loopChart(percentage: Double) -> some View { @@ -46,6 +46,14 @@ struct LoopsView: View { .foregroundStyle( percentage >= 90 ? Color(.darkGreen) : percentage >= 75 ? .orange : .red ) + .clipShape( + UnevenRoundedRectangle( + topLeadingRadius: 4, + bottomLeadingRadius: 4, + bottomTrailingRadius: 4, + topTrailingRadius: 4 + ) + ) .annotation(position: .overlay) { Text(percentage.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) + " %") .font(.loopFont) diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/TIRView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/TIRView.swift index f8cac5e06d..af2ddeb8d4 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/TIRView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/TIRView.swift @@ -19,14 +19,7 @@ struct PreviewChart: View { var body: some View { let fetched = previewTir() - struct TIRinPercent: Identifiable { - let type: String - let group: String - let percentage: Decimal - let id: UUID - } - - let separator: Decimal = 4 + let separator: Decimal = 3 var data: [TIRinPercent] = [ TIRinPercent( @@ -36,13 +29,19 @@ struct PreviewChart: View { comment: "" ), percentage: fetched[4].decimal, - id: UUID() + id: UUID(), + offset: -5, + first: true, + last: false ), TIRinPercent( type: "TIR", group: "Separator", percentage: separator, - id: UUID() + id: UUID(), + offset: 0, + first: false, + last: false ), TIRinPercent( type: "TIR", @@ -51,25 +50,37 @@ struct PreviewChart: View { comment: "" ), percentage: fetched[0].decimal, - id: UUID() + id: UUID(), + offset: -10, + first: false, + last: false ), TIRinPercent( type: "TIR", group: "Separator", percentage: separator, - id: UUID() + id: UUID(), + offset: 0, + first: false, + last: false ), TIRinPercent( type: "TIR", group: NSLocalizedString("In Range", comment: ""), percentage: fetched[1].decimal, - id: UUID() + id: UUID(), + offset: 0, + first: false, + last: false ), TIRinPercent( type: "TIR", group: "Separator", percentage: separator, - id: UUID() + id: UUID(), + offset: 0, + first: false, + last: false ), TIRinPercent( type: "TIR", @@ -78,13 +89,19 @@ struct PreviewChart: View { comment: "" ), percentage: fetched[2].decimal, - id: UUID() + id: UUID(), + offset: 10, + first: false, + last: false ), TIRinPercent( type: "TIR", group: "Separator", percentage: separator, - id: UUID() + id: UUID(), + offset: 0, + first: false, + last: false ), TIRinPercent( type: "TIR", @@ -93,15 +110,15 @@ struct PreviewChart: View { comment: "" ), percentage: fetched[3].decimal, - id: UUID() + id: UUID(), + offset: 5, + first: false, + last: true ) ] - for index in 0 ..< 3 { - if data[index].percentage == 0 { - data.remove(at: index + 1) - } - } + // Preapre the data array + data = prepareData(data_: data) return VStack { Text("Time In Range").padding(.bottom, 10).font(.previewHeadline) @@ -112,8 +129,15 @@ struct PreviewChart: View { y: .value("Percentage", item.percentage), width: .fixed(60) ) - .cornerRadius(2, style: .continuous) .foregroundStyle(by: .value("Group", item.group)) + .clipShape( + UnevenRoundedRectangle( + topLeadingRadius: (item.last || item.percentage == 100) ? 4 : 0, + bottomLeadingRadius: (item.first || item.percentage == 100) ? 4 : 0, + bottomTrailingRadius: (item.first || item.percentage == 100) ? 4 : 0, + topTrailingRadius: (item.last || item.percentage == 100) ? 4 : 0 + ) + ) .annotation(position: .trailing) { if item.group == NSLocalizedString("In Range", comment: ""), item.percentage > 0 { HStack { @@ -137,6 +161,7 @@ struct PreviewChart: View { } Text(item.group) } + .offset(x: 0, y: item.offset) .font(.loopFont) .padding(.leading, 10) } else if item.group == NSLocalizedString( @@ -151,6 +176,7 @@ struct PreviewChart: View { } Text(item.group) } + .offset(x: 0, y: item.offset) .font(.loopFont) .padding(.leading, 10) } else if item.group == NSLocalizedString( @@ -165,7 +191,7 @@ struct PreviewChart: View { } Text(item.group) } - .offset(x: 0, y: -5) + .offset(x: 0, y: item.offset) .font(.loopFont) .padding(.leading, 10) } else if item.group == NSLocalizedString( @@ -180,7 +206,7 @@ struct PreviewChart: View { } Text(item.group) } - .offset(x: 0, y: 5) + .offset(x: 0, y: item.offset) .font(.loopFont) .padding(.leading, 10) } @@ -214,7 +240,7 @@ struct PreviewChart: View { .offset(x: -UIScreen.main.bounds.width / 5, y: 0) }.frame(maxHeight: 200) .padding(.top, 20) - .dynamicTypeSize(...DynamicTypeSize.accessibility1) + .dynamicTypeSize(...DynamicTypeSize.xLarge) } private func previewTir() -> [(decimal: Decimal, string: String)] { @@ -256,4 +282,60 @@ struct PreviewChart: View { return array } + + private func prepareData(data_: [TIRinPercent]) -> [TIRinPercent] { + var data = data_ + + // Remove separators when needed + for index in 0 ..< data.count - 2 { + if index < data.count - 1 { + if data[index].percentage == 0 { + if index + 1 < data.count { + data.remove(at: index + 1) + } else { data.remove(at: data.count - 1) } + } + } + } + + if data.last?.group == "Separator" || (data.last?.percentage ?? 0) <= 0 { + data = data.dropLast() + // data.remove(at: data.count - 1) + } + + // Remove double separators + var c = false + for index in 0 ..< data.count - 1 { + if data[index].group == "Separator" { + if c { + data.remove(at: index) + } + c = true + } else { c = false } + } + + data.removeAll(where: { $0.percentage <= 0 }) + + // Update properties? + if (data.last?.percentage ?? 0) > 0, data.last?.group != "Separator" { + data[data.count - 1].last = true + } else { + data = data.dropLast() + data[data.count - 1].last = true + } + + data[0].first = true + data[data.count - 1].last = true + + return data + } +} + +struct TIRinPercent: Identifiable { + let type: String + let group: String + let percentage: Decimal + let id: UUID + let offset: CGFloat + var first: Bool + var last: Bool } diff --git a/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift b/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift index c2bb5e640c..7527ddcefb 100644 --- a/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift +++ b/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift @@ -32,7 +32,7 @@ extension Sharing { var body: some View { Form { Section { - Toggle("Share all of your Statistics", isOn: $state.uploadStats) + Toggle("Share and Backup all of your Settings and Statistics", isOn: $state.uploadStats) if state.uploadStats { Picker("Sex", selection: $state.sex) { ForEach(Sex.allCases) { sex in @@ -46,7 +46,12 @@ extension Sharing { .datePickerStyle(.compact) } } - } header: { Text("Statistics") } + } header: { Text("Upload Settings and Statistics") } + footer: { + Text( + "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." + ) + } if !state.uploadStats { Section { @@ -57,7 +62,7 @@ extension Sharing { Section {} footer: { Text( - "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." + "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." ) } @@ -76,8 +81,13 @@ extension Sharing { } } } - header: { Text("Your identifier") } - footer: { Text((copied && display) ? "Copied" : "") } + header: { Text("\nYour recovery token") } + + footer: { + Text((copied && display) ? "" : display ? "Long press to copy" : "") + .foregroundStyle((display && !copied) ? .blue : .secondary) + .frame(maxWidth: .infinity, alignment: .center) + } Section {} footer: { @@ -95,7 +105,7 @@ extension Sharing { configureView() state.savedSettings() } - .navigationBarTitle("Share your data anonymously") + .navigationBarTitle("Share and Backup") } } } diff --git a/FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift b/FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift index 8f7ad65b94..c8f15b3aa6 100644 --- a/FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift +++ b/FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift @@ -17,6 +17,7 @@ extension StatConfig { @Published var timeSettings: Bool = true @Published var minimumSMB: Decimal = 0.3 @Published var useInsulinBars: Bool = false + @Published var skipGlucoseChart: Bool = false var units: GlucoseUnits = .mmolL @@ -28,6 +29,7 @@ extension StatConfig { subscribeSetting(\.xGridLines, on: $xGridLines) { xGridLines = $0 } subscribeSetting(\.yGridLines, on: $yGridLines) { yGridLines = $0 } subscribeSetting(\.rulerMarks, on: $rulerMarks) { rulerMarks = $0 } + subscribeSetting(\.skipGlucoseChart, on: $skipGlucoseChart) { skipGlucoseChart = $0 } subscribeSetting(\.timeSettings, on: $timeSettings) { timeSettings = $0 } subscribeSetting(\.alwaysUseColors, on: $alwaysUseColors) { alwaysUseColors = $0 } subscribeSetting(\.useFPUconversion, on: $useFPUconversion) { useFPUconversion = $0 } diff --git a/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift b/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift index 0c073008c7..c68125b5d7 100644 --- a/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift +++ b/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift @@ -61,6 +61,7 @@ extension StatConfig { footer: { Text("In case you're using both profiles and temp targets") } Section { + Toggle("Never display the small glucose chart when scrolling", isOn: $state.skipGlucoseChart) Toggle("Always Color Glucose Value (green, yellow etc)", isOn: $state.alwaysUseColors) } header: { Text("Header settings") } footer: { Text("Normally glucose is colored red only when over or under your notification limits for high/low") } diff --git a/FreeAPS/Sources/Shortcuts/AppShortcuts.swift b/FreeAPS/Sources/Shortcuts/AppShortcuts.swift index e935dd8ab6..3a8a057233 100644 --- a/FreeAPS/Sources/Shortcuts/AppShortcuts.swift +++ b/FreeAPS/Sources/Shortcuts/AppShortcuts.swift @@ -45,5 +45,12 @@ import Foundation "\(.applicationName) Enacts a bolus" ] ) + AppShortcut( + intent: ApplyMealPresetIntent(), + phrases: [ + "\(.applicationName) Meal Presets", + "Uses an \(.applicationName) Meal Preset" + ] + ) } } diff --git a/FreeAPS/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift b/FreeAPS/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift index ad69a3a7db..45d6b649c7 100644 --- a/FreeAPS/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift +++ b/FreeAPS/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift @@ -4,7 +4,7 @@ import Foundation @available(iOS 16.0,*) final class CarbPresetIntentRequest: BaseIntentsRequest { func addCarbs(_ quantityCarbs: Double, _ quantityFat: Double, _ quantityProtein: Double, _ dateAdded: Date) throws -> String { guard quantityCarbs >= 0.0 || quantityFat >= 0.0 || quantityProtein >= 0.0 else { - return "no adding carbs in iAPS" + return "no carbs or carb equivalents to add" } let carbs = min(Decimal(quantityCarbs), settingsManager.settings.maxCarbs) diff --git a/FreeAPS/Sources/Shortcuts/Meals/MealsShortcuts.swift b/FreeAPS/Sources/Shortcuts/Meals/MealsShortcuts.swift new file mode 100644 index 0000000000..a17faf6413 --- /dev/null +++ b/FreeAPS/Sources/Shortcuts/Meals/MealsShortcuts.swift @@ -0,0 +1,158 @@ +import AppIntents +import Foundation +import Intents + +@available(iOS 16.0, *) struct MealPresetEntity: AppEntity, Identifiable { + static var defaultQuery = MealPresetQuery() + var id: String + var displayRepresentation: DisplayRepresentation { + DisplayRepresentation(title: "\(id)") + } + + static var typeDisplayRepresentation: TypeDisplayRepresentation = "Meals" +} + +enum MealPresetIntentError: Error { + case StateIntentUnknownError + case NoPresets +} + +@available(iOS 16.0, *) struct ApplyMealPresetIntent: AppIntent { + static var title: LocalizedStringResource = "iAPS Meal Presets" + static var description = IntentDescription("Allow to use iAPS Meal Presets") + internal var intentRequest: MealPresetIntentRequest + + init() { + intentRequest = MealPresetIntentRequest() + } + + @Parameter(title: "Preset") var preset: MealPresetEntity? + + @Parameter( + title: "Confirm Before activating", + description: "If toggled, you will need to confirm before activating", + default: true + ) var confirmBeforeApplying: Bool + + static var parameterSummary: some ParameterSummary { + When(\ApplyMealPresetIntent.$confirmBeforeApplying, .equalTo, true, { + Summary("Applying \(\.$preset)") { + \.$confirmBeforeApplying + } + }, otherwise: { + Summary("Immediately applying \(\.$preset)") { + \.$confirmBeforeApplying + } + }) + } + + @MainActor func perform() async throws -> some ProvidesDialog { + do { + let presetToApply: MealPresetEntity + if let preset = preset { + presetToApply = preset + } else { + presetToApply = try await $preset.requestDisambiguation( + among: intentRequest.fetchPresets(), + dialog: "Which meal preset would you like to use?" + ) + } + + let displayName: String = presetToApply.id + if confirmBeforeApplying { + try await requestConfirmation( + result: .result(dialog: "Are you sure you want to use the meal preset \(displayName)?") + ) + } + + let preset = try intentRequest.findPreset(displayName) + let finalOverrideApply = try intentRequest.enactPreset(preset) + let isDone = finalOverrideApply != nil ? true : false + + let displayDetail: String = isDone ? + NSLocalizedString("The Meal", comment: "") + " \(displayName) " + + NSLocalizedString("has been added to iAPS", comment: "") : "Adding Meal Failed" + return .result( + dialog: IntentDialog(stringLiteral: displayDetail) + ) + } catch { + throw error + } + } +} + +@available(iOS 16.0, *) struct MealPresetQuery: EntityQuery { + internal var intentRequest: MealPresetIntentRequest + + init() { + intentRequest = MealPresetIntentRequest() + } + + func entities(for identifiers: [MealPresetEntity.ID]) async throws -> [MealPresetEntity] { + let presets = intentRequest.fetchIDs(identifiers) + return presets + } + + func suggestedEntities() async throws -> [MealPresetEntity] { + let presets = try intentRequest.fetchPresets() + return presets + } +} + +@available(iOS 16.0, *) final class MealPresetIntentRequest: BaseIntentsRequest { + func fetchPresets() throws -> ([MealPresetEntity]) { + let presets = coreDataStorage.fetchMealPresets().flatMap { preset -> [MealPresetEntity] in + [MealPresetEntity(id: preset.dish ?? "")] + } + return presets + } + + func findPreset(_ name: String) throws -> Presets { + let presetFound = coreDataStorage.fetchMealPresets().filter({ $0.dish == name }) + guard let preset = presetFound.first else { throw MealPresetIntentError.NoPresets } + return preset + } + + func fetchIDs(_: [MealPresetEntity.ID]) -> [MealPresetEntity] { + let presets = coreDataStorage.fetchMealPresets() + .map { preset -> MealPresetEntity in + let dish = preset.dish ?? "" + return MealPresetEntity(id: dish) + } + return presets + } + + func enactPreset(_ preset: Presets) throws -> String? { + guard let mealPreset = coreDataStorage.fetchMealPreset(preset.dish ?? "") else { + return nil + } + + let quantityCarbs = (mealPreset.carbs ?? 0) as Decimal + let quantityFat = (mealPreset.fat ?? 0) as Decimal + let quantityProtein = (mealPreset.protein ?? 0) as Decimal + + guard quantityCarbs >= 0.0 || quantityFat >= 0.0 || quantityProtein >= 0.0 else { + return nil + } + + let carbs = min(quantityCarbs, settingsManager.settings.maxCarbs) + let now = Date.now + + carbsStorage.storeCarbs( + [CarbsEntry( + id: UUID().uuidString, + createdAt: now, + actualDate: now, + carbs: carbs, + fat: quantityFat, + protein: quantityProtein, + note: mealPreset.dish ?? "", + enteredBy: CarbsEntry.manual, + isFPU: (quantityFat > 0 || quantityProtein > 0) ? true : false, + fpuID: (quantityFat > 0 || quantityProtein > 0) ? UUID().uuidString : nil + )] + ) + + return "OK" + } +} diff --git a/FreeAPS/Sources/Views/ViewModifiers.swift b/FreeAPS/Sources/Views/ViewModifiers.swift index 6d46dbcbd0..5e2d71ae00 100644 --- a/FreeAPS/Sources/Views/ViewModifiers.swift +++ b/FreeAPS/Sources/Views/ViewModifiers.swift @@ -49,6 +49,27 @@ struct CompactSectionSpacing: ViewModifier { } } +struct ScrollTargetLayoutModifier: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 17, *) { + return content + .scrollTargetLayout() + } else { + return content } + } +} + +struct ScrollPositionModifier: ViewModifier { + @Binding var id: Int? + func body(content: Content) -> some View { + if #available(iOS 17, *) { + return content + .scrollPosition(id: $id) + } else { + return content } + } +} + struct AddShadow: ViewModifier { @Environment(\.colorScheme) var colorScheme func body(content: Content) -> some View { @@ -156,7 +177,7 @@ struct TimeEllipse: View { let characters: Int var body: some View { RoundedRectangle(cornerRadius: 15) - .fill(Color.gray).opacity(colorScheme == .light ? 0.2 : 0.08) + .fill(Color.gray).opacity(colorScheme == .light ? 0.2 : 0.2) .frame(width: CGFloat(characters * 7), height: 25) } } @@ -324,6 +345,14 @@ extension View { modifier(CompactSectionSpacing()) } + func scrollTargetLayoutiOS17() -> some View { + modifier(ScrollTargetLayoutModifier()) + } + + func scrollPositioniOS17(id: Binding) -> some View { + modifier(ScrollPositionModifier(id: id)) + } + func asAny() -> AnyView { .init(self) } } From c5454af039915a26a902fe602875c935290a863c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Thu, 1 Aug 2024 20:38:13 +0200 Subject: [PATCH 04/12] V4.8.0 Omnipod updates ported by @itsmojo: LoopKit/OmniBLE#125 & LoopKit/OmniKit#36, fix bolusState & basalDeliveryState use !isFinished() again LoopKit/OmniKit#36, update OmniKit setStateWithResult() to match OmniBLE version LoopKit/OmniBLE#126 & LoopKit/OmniKit#37, fix incorrect pod suspended message on bolus after an error Remove no longer needed APSManager updateStatus() to avoid extra get pod status commands Nightscout Glucose backfill resolved: #775 Resolves several old issues with import/upload to Nightscout and to the statistics database. Prepare for the configuration profiles and the onboarding Views coming in v4.9, for instance backup of your CoreData. f0d1d52 Make Oref0 error string in pop-up more accessible: 41d2b44 Localization of the Sharing strings, by @Mirko-tri Crowdin translations from translators: Hung Nguyen Phuteleco, @Mirko-tri, Abdulrahman Alfantokh, @vanzaam, Mykola Yroslavadudko and Typ1er. --- Config.xcconfig | 2 +- .../Core_Data.xcdatamodel/contents | 16 +- .../CGMBLEKit/ar.lproj/Localizable.strings | 8 +- .../CGMBLEKitUI/ar.lproj/Localizable.strings | 6 +- .../ar.lproj/TransmitterManagerSetup.strings | 4 +- .../ar.lproj/Localizable.strings | 50 +-- .../G7SensorKit/ar.lproj/Localizable.strings | 68 +-- .../Resources/vi.lproj/Localizable.strings | 7 +- .../vi.lproj/Localizable.strings | 28 +- .../PumpManager/OmniBLEPumpManager.swift | 26 +- .../OmniBLE/PumpManager/PodCommsSession.swift | 6 + .../PumpManager/OmnipodPumpManager.swift | 50 ++- .../PumpManager/OmnipodPumpManagerState.swift | 2 + .../OmniKit/PumpManager/PodCommsSession.swift | 6 + FreeAPS.xcodeproj/project.pbxproj | 74 +-- FreeAPS/Sources/APS/APSManager.swift | 18 - FreeAPS/Sources/APS/OpenAPS/Constants.swift | 5 + .../Sources/APS/Storage/CoreDataStorage.swift | 128 ++++++ .../Main/ar.lproj/Localizable.strings | 55 ++- .../Main/da.lproj/Localizable.strings | 53 ++- .../Main/de.lproj/Localizable.strings | 55 ++- .../Main/en.lproj/Localizable.strings | 50 ++- .../Main/es.lproj/Localizable.strings | 53 ++- .../Main/fi.lproj/Localizable.strings | 53 ++- .../Main/fr.lproj/Localizable.strings | 135 +++--- .../Main/he.lproj/Localizable.strings | 53 ++- .../Main/hu.lproj/Localizable.strings | 53 ++- .../Main/it.lproj/Localizable.strings | 53 ++- .../Main/nb.lproj/Localizable.strings | 53 ++- .../Main/nl.lproj/Localizable.strings | 59 ++- .../Main/pl.lproj/Localizable.strings | 53 ++- .../Main/pt-BR.lproj/Localizable.strings | 53 ++- .../Main/pt-PT.lproj/Localizable.strings | 53 ++- .../Main/ru.lproj/Localizable.strings | 53 ++- .../Main/sk.lproj/Localizable.strings | 53 ++- .../Main/sv.lproj/Localizable.strings | 53 ++- .../Main/tr.lproj/Localizable.strings | 53 ++- .../Main/uk.lproj/Localizable.strings | 53 ++- .../Main/vi.lproj/Localizable.strings | 103 +++-- .../Main/zh-Hans.lproj/Localizable.strings | 53 ++- FreeAPS/Sources/Models/BloodGlucose.swift | 72 ++- FreeAPS/Sources/Models/DatabaseModels.swift | 100 +++++ .../Models/NightscoutPreferences.swift | 7 - .../Sources/Models/NightscoutSettings.swift | 7 - .../Sources/Models/NightscoutStatistics.swift | 7 - FreeAPS/Sources/Models/NightscoutStatus.swift | 1 + .../DataTable/DataTableStateModel.swift | 5 +- .../Modules/Home/View/HomeRootView.swift | 2 +- .../Sharing/View/SharingRootView.swift | 4 +- .../Sources/Services/Network/Database.swift | 422 ++++++++++++++++++ .../Services/Network/NightscoutManager.swift | 408 ++++++++++++----- .../Services/Storage/FileStorage.swift | 12 +- 52 files changed, 2193 insertions(+), 663 deletions(-) create mode 100644 FreeAPS/Sources/Models/DatabaseModels.swift delete mode 100644 FreeAPS/Sources/Models/NightscoutPreferences.swift delete mode 100644 FreeAPS/Sources/Models/NightscoutSettings.swift delete mode 100644 FreeAPS/Sources/Models/NightscoutStatistics.swift create mode 100644 FreeAPS/Sources/Services/Network/Database.swift diff --git a/Config.xcconfig b/Config.xcconfig index 75a089bb5a..3ad19d0021 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -1,5 +1,5 @@ APP_DISPLAY_NAME = iAPS -APP_VERSION = 4.6.0 +APP_VERSION = 4.8.0 APP_BUILD_NUMBER = 1 COPYRIGHT_NOTICE = DEVELOPER_TEAM = ##TEAM_ID## diff --git a/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents b/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents index 4e30b53910..0f34a2efc9 100644 --- a/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents +++ b/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents @@ -1,5 +1,10 @@ - + + + + + + @@ -71,6 +76,10 @@ + + + + @@ -126,6 +135,11 @@ + + + + + diff --git a/Dependencies/CGMBLEKit/CGMBLEKit/ar.lproj/Localizable.strings b/Dependencies/CGMBLEKit/CGMBLEKit/ar.lproj/Localizable.strings index 603776c285..09777b4c0c 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKit/ar.lproj/Localizable.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKit/ar.lproj/Localizable.strings @@ -5,7 +5,7 @@ "Dexcom G6" = "ديكسكوم G6"; /* Error description for unreliable state */ -"Glucose data is unavailable" = "Glucose data is unavailable"; +"Glucose data is unavailable" = "قراءات السكر غير متوفرة"; /* Describes a low battery */ "Low Battery" = "البطارية منخفضة"; @@ -26,13 +26,13 @@ "Sensor calibration is OK" = "معايرة المستشعر مضبوطة"; /* The description of sensor calibration state when raw value is unknown. (1: missing data details) */ -"Sensor is in unknown state %1$d" = "Sensor is in unknown state %1$d"; +"Sensor is in unknown state %1$d" = "المستشعر في حالة غير معروفة %1$d"; /* The description of sensor calibration state when sensor sensor is stopped. */ -"Sensor is stopped" = "Sensor is stopped"; +"Sensor is stopped" = "المستشعر متوقف"; /* The description of sensor calibration state when sensor sensor is warming up. */ -"Sensor is warming up" = "Sensor is warming up"; +"Sensor is warming up" = "المستشعر تحت الإحماء"; /* The description of sensor calibration state when sensor needs calibration. */ "Sensor needs calibration" = "المستشعر بحاجة الى معايرة"; diff --git a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings index 19c2951fec..789c5839f8 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* Format string for glucose trend per minute. (1: glucose value and unit) */ -"%@/min" = "%@/min"; +"%@/min" = "%@ دقيقة"; /* Confirmation message for deleting a CGM */ "Are you sure you want to delete this CGM?" = "هل أنت متأكد أنك تريد حذف هذا CGM؟"; @@ -54,7 +54,7 @@ Title text for the button to remove a CGM from Loop */ "Transmitter ID" = "Transmitter ID"; /* Title describing glucose trend */ -"Trend" = "Trend"; +"Trend" = "إتجاه"; /* The title text for the upload glucose switch cell */ -"Upload Readings" = "Upload Readings"; +"Upload Readings" = "رفع القراءات"; diff --git a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings index b3b8878728..1ab79b565f 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings @@ -17,7 +17,7 @@ "k1N-Rg-XDy.footerTitle" = "يمكن تحميل البيانات عبر الإنترنت من تطبيق Share عند فشل اتصال المرسل."; /* Class = "UITableViewSection"; headerTitle = "Dexcom Share"; ObjectID = "k1N-Rg-XDy"; */ -"k1N-Rg-XDy.headerTitle" = "Dexcom Share"; +"k1N-Rg-XDy.headerTitle" = "ديكسكوم شير"; /* Class = "UITextField"; placeholder = "Enter the 6-digit transmitter ID"; ObjectID = "nKX-TW-GhD"; */ -"nKX-TW-GhD.placeholder" = "Enter the 6-digit transmitter ID"; +"nKX-TW-GhD.placeholder" = "أدخل معرف جهاز الإرسال المكون من 6 أرقام"; diff --git a/Dependencies/G7SensorKit/G7SensorKitUI/ar.lproj/Localizable.strings b/Dependencies/G7SensorKit/G7SensorKitUI/ar.lproj/Localizable.strings index 9b9a6b9523..90c967438f 100644 --- a/Dependencies/G7SensorKit/G7SensorKitUI/ar.lproj/Localizable.strings +++ b/Dependencies/G7SensorKit/G7SensorKitUI/ar.lproj/Localizable.strings @@ -2,13 +2,13 @@ "– – –" = "– – –"; /* Format string for glucose trend per minute. (1: glucose value and unit) */ -"%@/min" = "%@/min"; +"%@/min" = "%@ دقيقة"; /* No comment provided by engineer. */ "Are you sure you want to delete this CGM?" = "Are you sure you want to delete this CGM?"; /* No comment provided by engineer. */ -"Bluetooth" = "Bluetooth"; +"Bluetooth" = "البلوتوث"; /* Button text to cancel G7 setup */ "Cancel" = "Cancel"; @@ -30,7 +30,7 @@ /* Navigation bar title for G7SettingsView Title on WelcomeView */ -"Dexcom G7" = "Dexcom G7"; +"Dexcom G7" = "ديكسكوم G7"; /* No comment provided by engineer. */ "Done" = "Done"; @@ -39,79 +39,79 @@ "Glucose" = "Glucose"; /* title for g7 settings row showing sensor grace period end time */ -"Grace Period End" = "Grace Period End"; +"Grace Period End" = "نهاية الفترة"; /* G7 Progress bar label when sensor grace period progress showing */ -"Grace period remaining" = "Grace period remaining"; +"Grace period remaining" = "الفترة المتبقية"; /* String displayed instead of a glucose value above the CGM range */ -"HIGH" = "HIGH"; +"HIGH" = "مرتفع"; /* title for g7 settings row showing sensor last connect time */ -"Last Connect" = "Last Connect"; +"Last Connect" = "آخر اتصال"; /* No comment provided by engineer. */ -"Last Reading" = "Last Reading"; +"Last Reading" = "آخر قراءة"; /* Descriptive text on G7StartupView */ -"iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management." = "iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management."; +"iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management." = "يمكن لـ iAPS قراءة بيانات G7، ولكن لا يزال عليك استخدام تطبيق ديكسكوم G7 للإقران والمعايرة وإدارة المستشعرات الأخرى."; /* String displayed instead of a glucose value below the CGM range */ -"LOW" = "LOW"; +"LOW" = "منخفض"; /* title for g7 settings row showing BLE Name */ "Name" = "Name"; /* No comment provided by engineer. */ -"Scan for new sensor" = "Scan for new sensor"; +"Scan for new sensor" = "البحث عن مستشعر جديد"; /* title for g7 settings connection status when scanning */ -"Scanning" = "Scanning"; +"Scanning" = "يتم المسح"; /* G7 Status highlight text for searching for sensor */ -"Searching for\nSensor" = "Searching for\nSensor"; +"Searching for\nSensor" = "البحث عن مستشعر\n"; /* G7 Progress bar label when searching for sensor */ -"Searching for sensor" = "Searching for sensor"; +"Searching for sensor" = "البحث عن مستشعر"; /* G7 Status highlight text for sensor expired */ -"Sensor\nExpired" = "Sensor\nExpired"; +"Sensor\nExpired" = "إنتهاء صلاحية المستشعر\n"; /* G7 Status highlight text for sensor failed */ -"Sensor\nFailed" = "Sensor\nFailed"; +"Sensor\nFailed" = "فشل جهاز الاستشعار\n"; /* G7 Status highlight text for sensor error */ -"Sensor\nIssue" = "Sensor\nIssue"; +"Sensor\nIssue" = "مشكلة في المستشعر\n"; /* G7 Status highlight text for sensor warmup */ "Sensor\nWarmup" = "Sensor\nWarmup"; /* title for g7 settings row showing sensor expiration time */ -"Sensor Expiration" = "Sensor Expiration"; +"Sensor Expiration" = "إنتهت صلاحية المستشعر"; /* G7 Progress bar label when sensor expired */ -"Sensor expired" = "Sensor expired"; +"Sensor expired" = "إنتهت صلاحية المستشعر"; /* G7 Progress bar label when sensor lifetime progress showing */ -"Sensor expires" = "Sensor expires"; +"Sensor expires" = "إنتهت صلاحية المستشعر"; /* G7 Progress bar label when sensor failed */ -"Sensor failed" = "Sensor failed"; +"Sensor failed" = "فشل الحساس"; /* title for g7 settings row showing sensor start time */ "Sensor Start" = "Start sensor"; /* G7 Status highlight text for signal loss */ -"Signal\nLoss" = "Signal\nLoss"; +"Signal\nLoss" = "فقدان الإشارة\n"; /* Field label */ "Time" = "Time"; /* Field label */ -"Trend" = "Trend"; +"Trend" = "إتجاه"; /* title for g7 config settings to upload readings */ -"Upload Readings" = "Upload Readings"; +"Upload Readings" = "رفع القراءات"; /* G7 Progress bar label when sensor in warmup */ -"Warmup completes" = "Warmup completes"; +"Warmup completes" = "اكتمل الإحماء"; diff --git a/Dependencies/G7SensorKit/ar.lproj/Localizable.strings b/Dependencies/G7SensorKit/ar.lproj/Localizable.strings index cdf9de583c..a1087c7584 100644 --- a/Dependencies/G7SensorKit/ar.lproj/Localizable.strings +++ b/Dependencies/G7SensorKit/ar.lproj/Localizable.strings @@ -1,8 +1,8 @@ /* Title on WelcomeView */ -"Dexcom G7" = "Dexcom G7"; +"Dexcom G7" = "ديكسكوم G7"; /* Descriptive text on G7StartupView */ -"iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management." = "iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management."; +"iAPS can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management." = "يمكن لـ iAPS قراءة بيانات G7، ولكن لا يزال عليك استخدام تطبيق ديكسكوم G7 للإقران والمعايرة وإدارة المستشعرات الأخرى."; /* Button title for starting setup */ "Continue" = "Continue"; @@ -11,51 +11,51 @@ "Cancel" = "Cancel"; /* Error description for unreliable state */ -"Glucose data is unavailable" = "Glucose data is unavailable"; +"Glucose data is unavailable" = "قراءات السكر غير متوفرة"; /* The description of sensor algorithm state when 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 stopped"; +"Sensor is stopped" = "المستشعر متوقف"; /* The description of sensor algorithm state when sensor is warming up. */ -"Sensor is warming up" = "Sensor is warming up"; +"Sensor is warming up" = "المستشعر تحت الإحماء"; /* The description of sensor algorithm state when sensor is expired. */ -"Sensor expired" = "Sensor expired"; +"Sensor expired" = "إنتهت صلاحية المستشعر"; /* The description of sensor algorithm state when sensor failed. */ -"Sensor failed" = "Sensor failed"; +"Sensor failed" = "فشل الحساس"; /* The description of sensor algorithm state when raw value is unknown. (1: missing data details) */ -"Sensor is in unknown state %1$d" = "Sensor is in unknown state %1$d"; +"Sensor is in unknown state %1$d" = "المستشعر في حالة غير معروفة %1$d"; /* title for g7 settings row showing sensor start time */ -"Sensor Start" = "Sensor Start"; +"Sensor Start" = "ابدأ الحساس"; /* title for g7 settings row showing sensor expiration time */ -"Sensor Expiration" = "Sensor Expiration"; +"Sensor Expiration" = "إنتهت صلاحية المستشعر"; /* title for g7 settings row showing sensor grace period end time */ -"Grace Period End" = "Grace Period End"; +"Grace Period End" = "نهاية الفترة"; /* Field label */ "Glucose" = "Glucose"; -"Last Reading" = "Last Reading"; +"Last Reading" = "آخر قراءة"; "Time" = "Time"; -"Trend" = "Trend"; +"Trend" = "إتجاه"; -"Bluetooth" = "Bluetooth"; +"Bluetooth" = "البلوتوث"; /* title for g7 settings row showing BLE Name */ "Name" = "Name"; /* title for g7 settings connection status when scanning */ -"Scanning" = "Scanning"; +"Scanning" = "يتم المسح"; /* title for g7 settings connection status when connected */ "Connected" = "Connected"; @@ -64,16 +64,16 @@ "Connecting" = "Connecting"; /* title for g7 settings row showing sensor last connect time */ -"Last Connect" = "Last Connect"; +"Last Connect" = "آخر اتصال"; /* Configuration */ "Configuration" = "Configuration"; /* title for g7 config settings to upload readings */ -"Upload Readings" = "Upload Readings"; +"Upload Readings" = "رفع القراءات"; /* Button */ -"Scan for new sensor" = "Scan for new sensor"; +"Scan for new sensor" = "البحث عن مستشعر جديد"; /* Button label for removing CGM */ "Delete CGM" = "Delete CGM"; @@ -81,49 +81,49 @@ /* No glucose value representation (3 dashes for mg/dL) */ "– – –" = "– – –"; /* String displayed instead of a glucose value below the CGM range */ -"LOW" = "LOW"; +"LOW" = "منخفض"; /* String displayed instead of a glucose value above the CGM range */ -"HIGH" = "HIGH"; +"HIGH" = "مرتفع"; /* Format string for glucose trend per minute. (1: glucose value and unit) */ -"%@/min" = "%@/min"; +"%@/min" = "%@ دقيقة"; /* G7 Progress bar label when searching for sensor */ -"Searching for sensor" = "Searching for sensor"; +"Searching for sensor" = "البحث عن مستشعر"; /* G7 Progress bar label when sensor expired */ -"Sensor expired" = "Sensor expired"; +"Sensor expired" = "إنتهت صلاحية المستشعر"; /* G7 Progress bar label when sensor in warmup */ -"Warmup completes" = "Warmup completes"; +"Warmup completes" = "اكتمل الإحماء"; /* G7 Progress bar label when sensor in warmup */ -"Warmup completes" = "Warmup completes"; +"Warmup completes" = "اكتمل الإحماء"; /* G7 Progress bar label when sensor failed */ -"Sensor failed" = "Sensor failed"; +"Sensor failed" = "فشل الحساس"; /* G7 Progress bar label when sensor lifetime progress showing */ -"Sensor expires" = "Sensor expires"; +"Sensor expires" = "إنتهت صلاحية المستشعر"; /* G7 Progress bar label when sensor grace period progress showing */ -"Grace period remaining" = "Grace period remaining"; +"Grace period remaining" = "الفترة المتبقية"; /* G7 Status highlight text for searching for sensor */ -"Searching for\nSensor" = "Searching for\nSensor"; +"Searching for\nSensor" = "البحث عن مستشعر\n"; /* G7 Status highlight text for sensor expired */ -"Sensor\nExpired" = "Sensor\nExpired"; +"Sensor\nExpired" = "إنتهاء صلاحية المستشعر\n"; /* G7 Status highlight text for signal loss */ -"Sensor\nFailed" = "Sensor\nFailed"; +"Sensor\nFailed" = "فشل جهاز الاستشعار\n"; /* G7 Status highlight text for signal loss */ -"Signal\nLoss" = "Signal\nLoss"; +"Signal\nLoss" = "فقدان الإشارة\n"; /*G7 Status highlight text for sensor error */ -"Sensor\nIssue" = "Sensor\nIssue"; +"Sensor\nIssue" = "مشكلة في المستشعر\n"; /* G7 Status highlight text for sensor warmup */ "Sensor\nWarmup" = "Sensor\nWarmup"; diff --git a/Dependencies/MinimedKit/MinimedKitUI/Resources/vi.lproj/Localizable.strings b/Dependencies/MinimedKit/MinimedKitUI/Resources/vi.lproj/Localizable.strings index 03b4bdf7e7..e06356aaf7 100644 --- a/Dependencies/MinimedKit/MinimedKitUI/Resources/vi.lproj/Localizable.strings +++ b/Dependencies/MinimedKit/MinimedKitUI/Resources/vi.lproj/Localizable.strings @@ -11,7 +11,7 @@ "%1$@ basal schedule entries\n" = "%1$@ Lịch biểu liều nền\n"; /* The format string describing units of insulin remaining: (1: number of units) */ -"%1$@ Units of insulin remaining\n" = "%1$@ Số unit insulin còn lại\n"; +"%1$@ Units of insulin remaining\n" = "%1$@ Số đơn vị insulin còn lại\n"; /* Accessibility format string for (1: localized volume)(2: time) */ "%1$@ units remaining at %2$@" = "%1$@ units vẫn đang còn lúc %2$@"; @@ -99,7 +99,7 @@ "Insulin Delivery" = "Khối lượng tiêm insulin"; /* Instructions on selecting an insulin data source */ -"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "Việc tiêm insulin có thể được quyết định từ bơm bằng cách kết hợp giải thuật dữ liệu của người sử dụng và so sánh với khối lượngngăn chứa insulin theo thời gian. Việc đọc các dữ liệu cũ sẽ đảm bảo biểu đồ đường huyết luôn được tính chính xác và tải dữ liệu điều trị cập nhật lên Nightscout, nhưng lại tăng việc tiêu hao pin cũng như lỗi giao tiếp tần số radio cao hơn việc chỉ đọc mỗi dữ liệu ngăn chứa insulin. Trong trường hợp nguồn dữ liệu không được lựa chọn vì bất kỳ lý do gì thì phần mềm sẽ quay sang lựa chọn khác."; +"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "Việc tiêm insulin có thể được quyết định từ bơm bằng cách kết hợp giải thuật dữ liệu của người sử dụng và so sánh với khối lượng ngăn chứa insulin theo thời gian. Việc đọc các dữ liệu cũ sẽ đảm bảo biểu đồ đường huyết luôn được tính chính xác và tải dữ liệu điều trị cập nhật lên Nightscout, nhưng lại tăng việc tiêu hao pin cũng như lỗi giao tiếp tần số radio cao hơn việc chỉ đọc mỗi dữ liệu ngăn chứa insulin. Trong trường hợp nguồn dữ liệu không được lựa chọn vì bất kỳ lý do gì thì phần mềm sẽ quay sang lựa chọn khác."; /* Header for insulin remaining on pod settings screen */ "Insulin Remaining" = "Insulin còn lại"; @@ -123,8 +123,7 @@ "No, Keep Pump As Is" = "Không, Giữ nguyên máy bơm"; /* 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" = "Trên máy bơm của bạn, hãy đi tới \"Find Device\". Chọn Main Menu >\Utilities >\\Connect Devices >\\Other Devices >\\On >\\Find Device. -Bơm sẽ tìm thấy chỉ định"; +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "Trên máy bơm của bạn, hãy đi tới \"Find Device\". Chọn Main Menu >\Utilities >\\Connect Devices >\\Other Devices >\\On >\\Find Device"; /* navigation title for pump battery type selection Text for medtronic pump preferred data source */ diff --git a/Dependencies/OmniBLE/Localizations/vi.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/vi.lproj/Localizable.strings index eee91e908d..b9271baeeb 100644 --- a/Dependencies/OmniBLE/Localizations/vi.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/vi.lproj/Localizable.strings @@ -9,7 +9,7 @@ "Multiple Command Alert" = "Multiple Command Alert"; /* Alert content title for userPodExpiration pod alert */ -"Pod Expiration Reminder" = "Lời Nhắc Pod Hết hạn"; +"Pod Expiration Reminder" = "Nhắc nhở hết hạn"; /* Alert content title for podExpiring pod alert */ "Pod Expired" = "Pod đã hết hạn"; @@ -18,7 +18,7 @@ "Low Reservoir" = "Sắp hết thuốc"; /* Alert content title for suspendInProgress pod alert */ -"Suspend In Progress Reminder" = "Tạm dừng lời nhắc đang tiến hành"; +"Suspend In Progress Reminder" = "Đang tiến hành tạm dừng lời nhắc"; /* Alert content title for suspendEnded pod alert */ "Resume Insulin" = "Tiếp tục lại việc tiêm insulin"; @@ -27,7 +27,7 @@ "Pod Pairing Incomplete" = "Pod ghép nối không thành công"; /* Alert content title for timeOffsetChangeDetected pod alert */ -"Time Change Detected" = "Thay đổi thời gian được phát hiện"; +"Time Change Detected" = "Đã phát hiện thay đổi thời gian"; /* Alert content body for multiCommand pod alert */ "Multiple Command Alert" = "Multiple Command Alert"; @@ -45,19 +45,19 @@ "%1$@ insulin or less remaining in Pod. Change Pod soon." = "%1$@ insulin hoặc ít hơn còn lại trong Pod. Thay Pod ngay."; /* Alert content body for suspendInProgress pod alert */ -"Suspend In Progress Reminder" = "Tạm dừng lời nhắc đang tiến hành"; +"Suspend In Progress Reminder" = "Đang tiến hành tạm dừng lời nhắc"; /* Alert content body for suspendEnded pod alert */ "The insulin suspension period has ended.\n\nYou can resume delivery from the banner on the home screen or from your pump settings screen. You will be reminded again in 15 minutes." = "Thời gian tạm ngưng insulin đã kết thúc.\n\n Bạn có thể phục hồi việc tiêm thuốc từ màn hình chính hoặc từ màn hình cài đặt bơm. Sẽ có thông báo nhắc trong vòng 15 phút."; /* Alert content body for finishSetupReminder pod alert */ -"Please finish pairing your pod." = "Đề nghị hoàn thành ghép đôi pod."; +"Please finish pairing your pod." = "Vui lòng hoàn tất việc ghép nối pod của bạn."; /* Alert content body for timeOffsetChangeDetected pod alert */ "The time on your pump is different from the current time. You can review the pump time and and sync to current time in settings." = "Thời gian trên bơm khác so với thời gian thực tế. Bạn có thể xem thời gian trên bơm và sync thời gian hiện hành trong cài đặt."; /* Alert notification body for suspendEnded pod alert user notification */ -"Suspension time is up. Open the app and resume." = "Thời gian tạm dừng hết. Mở ứng dụng và tiếp tục lại."; +"Suspension time is up. Open the app and resume." = "Đã hết thời gian tạm dừng. Mở ứng dụng và tiếp tục."; /* Action button default text for PodAlerts */ "Ok" = "Ok"; @@ -237,7 +237,7 @@ "No Pod" = "Không pod"; /* Status highlight message for emptyReservoir alarm. */ -"No Insulin" = "Hết thuốc"; +"No Insulin" = "Hết Insulin"; /* Status highlight message for podExpired alarm. */ "Pod Expired" = "Pod đã hết hạn"; @@ -249,7 +249,7 @@ "Pod Error" = "Lỗi Pod"; /* Status highlight that a pump is out of insulin. */ -"No Insulin" = "Hết thuốc"; +"No Insulin" = "Hết Insulin"; /* Status highlight that insulin delivery was suspended. */ "Insulin Suspended" = "Insulin Đã tạm ngưng"; @@ -273,7 +273,7 @@ "Checking..." = "Đang kiểm tra..."; /* */ -"Check cannula insertion finished" = "Kiểm tra việc gắn cannula hoàn tất"; +"Check cannula insertion finished" = "Kiểm tra việc gắn cannula đã hoàn tất"; /* */ "Get pod status" = "Lấy thông tin pod"; @@ -538,7 +538,7 @@ "Pairing..." = "Đang ghép đôi..."; /* Pod pairing action button text while priming */ -"Priming..." = "Priming..."; +"Priming..." = "Đang chuẩn bị..."; /* */ "Deactivating..." = "Đang hủy kích hoạt..."; @@ -706,7 +706,7 @@ "Active Time" = "Thời gian Hoạt động"; /* description label for last status date pod details row */ -"Last Status" = "Trạng thái cuối"; +"Last Status" = "Trạng thái gần nhất"; /* description label for pod fault details */ "Pod Fault Details" = "Thông tin lỗi Pod"; @@ -838,12 +838,12 @@ "Previous Pod Details" = "Thông tin của Pod trước đó"; /* Text for pump manager details navigation link */ -"Pump Manager Details" = "Chi tiết về Trình quản lý Máy bơm"; +"Pump Manager Details" = "Chi tiết về Trình quản lý Bơm"; /* button title when retrieving pump manager details */ -"Retrieving Pump Manager Details..." = "Đang truy xuất thông tin Trình quản lý máy bơm..."; +"Retrieving Pump Manager Details..." = "Đang truy xuất thông tin Trình quản lý Bơm..."; /* button title to refresh pump manager details */ -"Refresh Pump Manager Details" = "Làm mới Chi tiết về Trình quản lý Máy bơm"; +"Refresh Pump Manager Details" = "Làm mới Chi tiết về Trình quản lý Bơm"; /* Section header for diagnostic section */ "Diagnostics" = "Chẩn đoán"; diff --git a/Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 2ca87e4b11..93099843d2 100644 --- a/Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/Dependencies/OmniBLE/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -154,12 +154,12 @@ public class OmniBLEPumpManager: DeviceManager { oldValue = state let oldStatusEvaluationDate = state.lastStatusChange let oldHighlight = buildPumpStatusHighlight(for: oldValue, andDate: oldStatusEvaluationDate) - oldStatus = status(for: oldValue) + oldStatus = status(for: oldValue, at: oldStatusEvaluationDate) returnType = changes(&state) let newStatusEvaluationDate = Date() - let newStatus = status(for: state) + let newStatus = status(for: state, at: newStatusEvaluationDate) let newHighlight = buildPumpStatusHighlight(for: state, andDate: newStatusEvaluationDate) if oldStatus != newStatus || oldHighlight != newHighlight { @@ -302,13 +302,13 @@ extension OmniBLEPumpManager { podStateObservers.removeElement(observer) } - private func status(for state: OmniBLEPumpManagerState) -> PumpManagerStatus { + private func status(for state: OmniBLEPumpManagerState, at date: Date = Date()) -> PumpManagerStatus { return PumpManagerStatus( timeZone: state.timeZone, device: device(for: state), pumpBatteryChargeRemaining: nil, - basalDeliveryState: basalDeliveryState(for: state), - bolusState: bolusState(for: state), + basalDeliveryState: basalDeliveryState(for: state, at: date), + bolusState: bolusState(for: state, at: date), insulinType: state.insulinType, deliveryIsUncertain: state.podState?.needsCommsRecovery == true ) @@ -340,7 +340,7 @@ extension OmniBLEPumpManager { } } - private func basalDeliveryState(for state: OmniBLEPumpManagerState) -> PumpManagerStatus.BasalDeliveryState { + private func basalDeliveryState(for state: OmniBLEPumpManagerState, at date: Date = Date()) -> PumpManagerStatus.BasalDeliveryState { guard let podState = state.podState else { return .active(.distantPast) } @@ -367,7 +367,7 @@ extension OmniBLEPumpManager { case .disengaging: return .cancelingTempBasal case .stable: - if let tempBasal = podState.unfinalizedTempBasal { + if let tempBasal = podState.unfinalizedTempBasal, !tempBasal.isFinished(at: date) { return .tempBasal(DoseEntry(tempBasal)) } switch podState.suspendState { @@ -379,7 +379,7 @@ extension OmniBLEPumpManager { } } - private func bolusState(for state: OmniBLEPumpManagerState) -> PumpManagerStatus.BolusState { + private func bolusState(for state: OmniBLEPumpManagerState, at date: Date = Date()) -> PumpManagerStatus.BolusState { guard let podState = state.podState else { return .noBolus } @@ -390,7 +390,7 @@ extension OmniBLEPumpManager { case .disengaging: return .canceling case .stable: - if let bolus = podState.unfinalizedBolus { + if let bolus = podState.unfinalizedBolus, !bolus.isFinished(at: date) { return .inProgress(DoseEntry(bolus)) } } @@ -1841,8 +1841,8 @@ extension OmniBLEPumpManager: PumpManager { state.bolusEngageState = .engaging }) - if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended != false { - self.log.error("enactBolus: returning pod suspended error for bolus") + if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended == true { + self.log.error("Not enacting bolus because podState or last status received indicates pod is suspended") completion(.deviceState(PodCommsError.podSuspended)) return } @@ -1982,8 +1982,8 @@ extension OmniBLEPumpManager: PumpManager { return } - if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended != false { - self.log.info("Not enacting temp basal because podState indicates pod is suspended.") + if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended == true { + self.log.info("Not enacting temp basal because podState or last status received indicates pod is suspended") completion(.deviceState(PodCommsError.podSuspended)) return } diff --git a/Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift b/Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift index c31ae0dd28..abc693e252 100644 --- a/Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift +++ b/Dependencies/OmniBLE/OmniBLE/PumpManager/PodCommsSession.swift @@ -552,6 +552,12 @@ public class PodCommsSession { log.default("bolus: pod is still bolusing") return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) } + // If the pod setup is complete, also confirm that the pod is indeed not suspended + if podState.setupProgress == .completed && statusResponse.deliveryStatus.suspended { + log.default("bolus: pod is suspended") + return DeliveryCommandResult.certainFailure(error: .podSuspended) + } + log.default("bolus: get status response verifies pod is not bolusing and not suspended") } else { log.default("bolus: failed to read pod status to verify there is no bolus running") return DeliveryCommandResult.certainFailure(error: .noResponse) diff --git a/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift b/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift index 0888bcdd5b..5cf0113a4c 100644 --- a/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -146,13 +146,25 @@ public class OmnipodPumpManager: RileyLinkPumpManager { private func setStateWithResult(_ changes: (_ state: inout OmnipodPumpManagerState) -> ReturnType) -> ReturnType { var oldValue: OmnipodPumpManagerState! var returnType: ReturnType! + var shouldNotifyStatusUpdate = false + var oldStatus: PumpManagerStatus? + let newValue = lockedState.mutate { (state) in oldValue = state + let oldStatusEvaluationDate = state.lastStatusChange + let oldHighlight = buildPumpStatusHighlight(for: oldValue, andDate: oldStatusEvaluationDate) + oldStatus = status(for: oldValue, at: oldStatusEvaluationDate) + returnType = changes(&state) - } - guard oldValue != newValue else { - return returnType + let newStatusEvaluationDate = Date() + let newStatus = status(for: state, at: newStatusEvaluationDate) + let newHighlight = buildPumpStatusHighlight(for: state, andDate: newStatusEvaluationDate) + + if oldStatus != newStatus || oldHighlight != newHighlight { + shouldNotifyStatusUpdate = true + state.lastStatusChange = newStatusEvaluationDate + } } if oldValue.podState != newValue.podState { @@ -173,25 +185,19 @@ public class OmnipodPumpManager: RileyLinkPumpManager { } } - // Ideally we ensure that oldValue.rawValue != newValue.rawValue, but the types aren't // defined as equatable pumpDelegate.notify { (delegate) in delegate?.pumpManagerDidUpdateState(self) } - let oldStatus = status(for: oldValue) - let newStatus = status(for: newValue) - - let oldHighlight = buildPumpStatusHighlight(for: oldValue) - let newHighlight = buildPumpStatusHighlight(for: newValue) - - if oldStatus != newStatus || oldHighlight != newHighlight { + if let oldStatus = oldStatus, shouldNotifyStatusUpdate { notifyStatusObservers(oldStatus: oldStatus) } return returnType } + private let lockedState: Locked private let statusObservers = WeakSynchronizedSet() @@ -431,13 +437,13 @@ extension OmnipodPumpManager { } } - private func status(for state: OmnipodPumpManagerState) -> PumpManagerStatus { + private func status(for state: OmnipodPumpManagerState, at date: Date = Date()) -> PumpManagerStatus { return PumpManagerStatus( timeZone: state.timeZone, device: device(for: state), pumpBatteryChargeRemaining: nil, - basalDeliveryState: basalDeliveryState(for: state), - bolusState: bolusState(for: state), + basalDeliveryState: basalDeliveryState(for: state, at: date), + bolusState: bolusState(for: state, at: date), insulinType: state.insulinType, deliveryIsUncertain: state.podState?.needsCommsRecovery == true ) @@ -469,7 +475,7 @@ extension OmnipodPumpManager { } } - private func basalDeliveryState(for state: OmnipodPumpManagerState) -> PumpManagerStatus.BasalDeliveryState { + private func basalDeliveryState(for state: OmnipodPumpManagerState, at date: Date = Date()) -> PumpManagerStatus.BasalDeliveryState { guard let podState = state.podState else { return .active(.distantPast) } @@ -496,7 +502,7 @@ extension OmnipodPumpManager { case .disengaging: return .cancelingTempBasal case .stable: - if let tempBasal = podState.unfinalizedTempBasal { + if let tempBasal = podState.unfinalizedTempBasal, !tempBasal.isFinished(at: date) { return .tempBasal(DoseEntry(tempBasal)) } switch podState.suspendState { @@ -508,7 +514,7 @@ extension OmnipodPumpManager { } } - private func bolusState(for state: OmnipodPumpManagerState) -> PumpManagerStatus.BolusState { + private func bolusState(for state: OmnipodPumpManagerState, at date: Date = Date()) -> PumpManagerStatus.BolusState { guard let podState = state.podState else { return .noBolus } @@ -519,7 +525,7 @@ extension OmnipodPumpManager { case .disengaging: return .canceling case .stable: - if let bolus = podState.unfinalizedBolus, !bolus.isFinished() { + if let bolus = podState.unfinalizedBolus, !bolus.isFinished(at: date) { return .inProgress(DoseEntry(bolus)) } } @@ -1856,8 +1862,8 @@ extension OmnipodPumpManager: PumpManager { state.bolusEngageState = .engaging }) - if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended != false { - self.log.error("enactBolus: returning pod suspended error for bolus") + if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended == true { + self.log.error("Not enacting bolus because podState or last status received indicates pod is suspended") completion(.deviceState(PodCommsError.podSuspended)) return } @@ -1995,8 +2001,8 @@ extension OmnipodPumpManager: PumpManager { return } - if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended != false { - self.log.info("Not enacting temp basal because podState indicates pod is suspended.") + if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended == true { + self.log.info("Not enacting temp basal because podState or last status received indicates pod is suspended") completion(.deviceState(PodCommsError.podSuspended)) return } diff --git a/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManagerState.swift index 3b4913d3a7..034afc25b7 100644 --- a/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ b/Dependencies/OmniKit/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -67,6 +67,8 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { internal var tempBasalEngageState: EngageablePumpState = .stable + internal var lastStatusChange: Date = .distantPast + internal var lastPumpDataReportDate: Date? internal var insulinType: InsulinType? diff --git a/Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift b/Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift index 9c8298dcde..ae3393d871 100644 --- a/Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift +++ b/Dependencies/OmniKit/OmniKit/PumpManager/PodCommsSession.swift @@ -527,6 +527,12 @@ public class PodCommsSession { log.default("bolus: pod is still bolusing") return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) } + // If the pod setup is complete, also confirm that the pod is indeed not suspended + if podState.setupProgress == .completed && statusResponse.deliveryStatus.suspended { + log.default("bolus: pod is suspended") + return DeliveryCommandResult.certainFailure(error: .podSuspended) + } + log.default("bolus: get status response verifies pod is not bolusing and not suspended") } else { log.default("bolus: failed to read pod status to verify there is no bolus running") return DeliveryCommandResult.certainFailure(error: .noResponse) diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index fb4b111bbe..fcc7422dcd 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ 190F8CF72BC6F70800EDB473 /* IllustrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190F8CF62BC6F70800EDB473 /* IllustrationView.swift */; }; 191A9D162BED00A500028D48 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191A9D152BED00A500028D48 /* Version.swift */; }; 191A9D182BED24B000028D48 /* ActiveIOBView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191A9D172BED24B000028D48 /* ActiveIOBView.swift */; }; - 191F62682AD6B05A004D7911 /* NightscoutSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191F62672AD6B05A004D7911 /* NightscoutSettings.swift */; }; 1920BF5D2B9DF53200E861FE /* BolusShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1920BF5C2B9DF53200E861FE /* BolusShortcut.swift */; }; 19229B962AFBB84800CD91CA /* Predictions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19229B952AFBB84800CD91CA /* Predictions.swift */; }; 192424CB2B7A64E70063CBF0 /* NIghtscoutExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192424CA2B7A64E70063CBF0 /* NIghtscoutExercise.swift */; }; @@ -31,6 +30,8 @@ 1935364028496F7D001E0B16 /* Dynamic structs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* Dynamic structs.swift */; }; 193F6CDD2A512C8F001240FD /* Loops.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193F6CDC2A512C8F001240FD /* Loops.swift */; }; 194297512B815938006B8A0B /* OverridesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194297502B815938006B8A0B /* OverridesView.swift */; }; + 19493A3B2C5997AD00EC83A7 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19493A3A2C5997AD00EC83A7 /* Database.swift */; }; + 19493A3D2C59987700EC83A7 /* DatabaseModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19493A3C2C59987700EC83A7 /* DatabaseModels.swift */; }; 194C32772B93A9BF0016FB2A /* OverrideShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194C32762B93A9BF0016FB2A /* OverrideShortcuts.swift */; }; 194D7E6E2B974F9F007A38C1 /* LoopsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D7E6D2B974F9F007A38C1 /* LoopsView.swift */; }; 1956FB212AFF79E200C7B4FF /* CoreDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1956FB202AFF79E200C7B4FF /* CoreDataStorage.swift */; }; @@ -53,7 +54,6 @@ 19AEF4322B1F5A98006FFE8B /* TIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19AEF4312B1F5A98006FFE8B /* TIRView.swift */; }; 19B0EF2128F6D66200069496 /* Statistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B0EF2028F6D66200069496 /* Statistics.swift */; }; 19C14F442C29807C009A7E07 /* ScrollOffset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C14F432C29807C009A7E07 /* ScrollOffset.swift */; }; - 19C3FB742C3878BE007AB7E6 /* AppShortcuts.strings in Resources */ = {isa = PBXBuildFile; fileRef = 19C3FB462C3878BE007AB7E6 /* AppShortcuts.strings */; }; 19D466A329AA2B80004D5F33 /* FPUConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A229AA2B80004D5F33 /* FPUConfigDataFlow.swift */; }; 19D466A529AA2BD4004D5F33 /* FPUConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */; }; 19D466A729AA2C22004D5F33 /* FPUConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A629AA2C22004D5F33 /* FPUConfigStateModel.swift */; }; @@ -451,8 +451,6 @@ F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */; }; F90692D6274B9A450037068D /* HealthKitStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D5274B9A450037068D /* HealthKitStateModel.swift */; }; FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */; }; - FE41E4D429463C660047FD55 /* NightscoutStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE41E4D329463C660047FD55 /* NightscoutStatistics.swift */; }; - FE41E4D629463EE20047FD55 /* NightscoutPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */; }; FE66D16B291F74F8005D6F77 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */; }; FEFA5C0F299F810B00765C17 /* Core_Data.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FEFA5C0D299F810B00765C17 /* Core_Data.xcdatamodeld */; }; FEFA5C11299F814A00765C17 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFA5C10299F814A00765C17 /* CoreDataStack.swift */; }; @@ -572,7 +570,6 @@ 1918333A26ADA46800F45722 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; 191A9D152BED00A500028D48 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; 191A9D172BED24B000028D48 /* ActiveIOBView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveIOBView.swift; sourceTree = ""; }; - 191F62672AD6B05A004D7911 /* NightscoutSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSettings.swift; sourceTree = ""; }; 1920BF5C2B9DF53200E861FE /* BolusShortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusShortcut.swift; sourceTree = ""; }; 192202902BAB567800B95BE8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 19229B952AFBB84800CD91CA /* Predictions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Predictions.swift; sourceTree = ""; }; @@ -599,6 +596,29 @@ 1927C8FB2744612600347C69 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; 1927C8FE274489BA00347C69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; 192A9A112C0F5361000BBC29 /* MealsShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealsShortcuts.swift; sourceTree = ""; }; + 192E77322B9F40C7004F4822 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77462B9F4CF1004F4822 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77472B9F4CF3004F4822 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77482B9F4CF6004F4822 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/AppShortcuts.strings"; sourceTree = ""; }; + 192E77492B9F4CF9004F4822 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E774A2B9F4CFB004F4822 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E774B2B9F4CFE004F4822 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E774C2B9F4D01004F4822 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E774D2B9F4D04004F4822 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E774E2B9F4D07004F4822 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E774F2B9F4D08004F4822 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77502B9F4D0A004F4822 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77512B9F4D0B004F4822 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77522B9F4D0D004F4822 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77532B9F4D0E004F4822 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/AppShortcuts.strings"; sourceTree = ""; }; + 192E77542B9F4D0F004F4822 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/AppShortcuts.strings"; sourceTree = ""; }; + 192E77552B9F4D11004F4822 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77562B9F4D12004F4822 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77572B9F4D14004F4822 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77582B9F4D1A004F4822 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E77592B9F4D1B004F4822 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E775A2B9F4D1C004F4822 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/AppShortcuts.strings; sourceTree = ""; }; + 192E775B2B9F4D1E004F4822 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/AppShortcuts.strings; sourceTree = ""; }; 1935363F28496F7D001E0B16 /* Dynamic structs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dynamic structs.swift"; sourceTree = ""; }; 193F1E392B44C13B00525770 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; 193F1E3A2B44C13B00525770 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; @@ -606,6 +626,8 @@ 193F1E3C2B44C14800525770 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; 193F6CDC2A512C8F001240FD /* Loops.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loops.swift; sourceTree = ""; }; 194297502B815938006B8A0B /* OverridesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridesView.swift; sourceTree = ""; }; + 19493A3A2C5997AD00EC83A7 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; + 19493A3C2C59987700EC83A7 /* DatabaseModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseModels.swift; sourceTree = ""; }; 194C32762B93A9BF0016FB2A /* OverrideShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideShortcuts.swift; sourceTree = ""; }; 194D7E6D2B974F9F007A38C1 /* LoopsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopsView.swift; sourceTree = ""; }; 1956FB202AFF79E200C7B4FF /* CoreDataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStorage.swift; sourceTree = ""; }; @@ -1053,8 +1075,6 @@ F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleHealthKitRootView.swift; sourceTree = ""; }; F90692D5274B9A450037068D /* HealthKitStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitStateModel.swift; sourceTree = ""; }; FBB3BAE7494CB771ABAC7B8B /* ISFEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorRootView.swift; sourceTree = ""; }; - FE41E4D329463C660047FD55 /* NightscoutStatistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutStatistics.swift; sourceTree = ""; }; - FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutPreferences.swift; sourceTree = ""; }; FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; }; FEFA5C0E299F810B00765C17 /* Core_Data.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Core_Data.xcdatamodel; sourceTree = ""; }; FEFA5C10299F814A00765C17 /* CoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = ""; }; @@ -1594,6 +1614,7 @@ 3811DE9725C9D88300A708ED /* NightscoutManager.swift */, 38FE826925CC82DB001FF17A /* NetworkService.swift */, 38FE826C25CC8461001FF17A /* NightscoutAPI.swift */, + 19493A3A2C5997AD00EC83A7 /* Database.swift */, ); path = Network; sourceTree = ""; @@ -1855,49 +1876,47 @@ 388E5A5925B6F0250019842D /* Models */ = { isa = PBXGroup; children = ( + CE82E02628E869DF00473A9C /* AlertEntry.swift */, 385CEAC025F2EA52002D6D5B /* Announcement.swift */, 388E5A5F25B6F2310019842D /* Autosens.swift */, 38A00B1E25FC00F7006BC0B0 /* Autotune.swift */, + 19F191E32BE686AE00F6297E /* BareMinimum.swift */, 388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */, 38D0B3B525EBE24900CB6E88 /* Battery.swift */, 382C134A25F14E3700715CE1 /* BGTargets.swift */, 3870FF4225EC13F40088248F /* BloodGlucose.swift */, 38A9260425F012D8009E3739 /* CarbRatios.swift */, 38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */, + 19D4E4EA29FC6A9F00351451 /* Charts.swift */, + 19A910352A24D6D700C8951B /* Configs.swift */, + F2159A532BA6207F00A0B716 /* ContactTrickEntry.swift */, 3811DF0125CA9FEA00A708ED /* Credentials.swift */, + 19493A3C2C59987700EC83A7 /* DatabaseModels.swift */, + 1935363F28496F7D001E0B16 /* Dynamic structs.swift */, + F270F68C2BAE374C00F6D8DD /* FontTracking.swift */, + F2159A512BA60F7A00A0B716 /* FontWeight.swift */, 38AEE73C25F0200C0013F05B /* FreeAPSSettings.swift */, 383948D925CD64D500E91849 /* Glucose.swift */, + E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */, + 1967DFBD29D052C200759F30 /* Icons.swift */, 382C133625F13A1E00715CE1 /* InsulinSensitivities.swift */, 38887CCD25F5725200944304 /* IOBEntry.swift */, + 193F6CDC2A512C8F001240FD /* Loops.swift */, + 19012CDB291D2CB900FB8210 /* LoopStats.swift */, + 192424CA2B7A64E70063CBF0 /* NIghtscoutExercise.swift */, 385CEA8125F23DFD002D6D5B /* NightscoutStatus.swift */, 389442CA25F65F7100FA1F27 /* NightscoutTreatment.swift */, 3895E4C525B9E00D00214B37 /* Preferences.swift */, 38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */, 3883583325EEB38000E024B2 /* PumpSettings.swift */, 38E989DC25F5021400C0CED0 /* PumpStatus.swift */, + CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */, 38BF021C25E7E3AF00579895 /* Reservoir.swift */, + 19B0EF2028F6D66200069496 /* Statistics.swift */, 3871F38625ED661C0013ECB5 /* Suggestion.swift */, 38A0364125ED069400FCBB52 /* TempBasal.swift */, 3871F39B25ED892B0013ECB5 /* TempTarget.swift */, 3811DE8E25C9D80400A708ED /* User.swift */, - E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */, - 1935363F28496F7D001E0B16 /* Dynamic structs.swift */, - CE82E02628E869DF00473A9C /* AlertEntry.swift */, - 19B0EF2028F6D66200069496 /* Statistics.swift */, - 19F191E32BE686AE00F6297E /* BareMinimum.swift */, - 19012CDB291D2CB900FB8210 /* LoopStats.swift */, - FE41E4D329463C660047FD55 /* NightscoutStatistics.swift */, - FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */, - 191F62672AD6B05A004D7911 /* NightscoutSettings.swift */, - 1967DFBD29D052C200759F30 /* Icons.swift */, - 19D4E4EA29FC6A9F00351451 /* Charts.swift */, - 19A910352A24D6D700C8951B /* Configs.swift */, - 193F6CDC2A512C8F001240FD /* Loops.swift */, - CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */, - 192424CA2B7A64E70063CBF0 /* NIghtscoutExercise.swift */, - F2159A512BA60F7A00A0B716 /* FontWeight.swift */, - F2159A532BA6207F00A0B716 /* ContactTrickEntry.swift */, - F270F68C2BAE374C00F6D8DD /* FontTracking.swift */, 191A9D152BED00A500028D48 /* Version.swift */, ); path = Models; @@ -2945,6 +2964,7 @@ 19E1F7EF29D08EBA005C8D20 /* IconConfigRootWiew.swift in Sources */, 1967DFC229D053D300759F30 /* IconImage.swift in Sources */, 382C134B25F14E3700715CE1 /* BGTargets.swift in Sources */, + 19493A3B2C5997AD00EC83A7 /* Database.swift in Sources */, 38AEE75725F0F18E0013F05B /* CarbsStorage.swift in Sources */, 38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */, 38AEE75225F022080013F05B /* SettingsManager.swift in Sources */, @@ -2995,7 +3015,6 @@ 191A9D162BED00A500028D48 /* Version.swift in Sources */, 38E44535274E411700EC9A94 /* Disk+Data.swift in Sources */, 3811DE3125C9D49500A708ED /* HomeProvider.swift in Sources */, - FE41E4D629463EE20047FD55 /* NightscoutPreferences.swift in Sources */, E013D872273AC6FE0014109C /* GlucoseSimulatorSource.swift in Sources */, 388E5A5C25B6F0770019842D /* JSON.swift in Sources */, 3811DF0225CA9FEA00A708ED /* Credentials.swift in Sources */, @@ -3057,7 +3076,6 @@ 38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */, 38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */, 3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */, - 191F62682AD6B05A004D7911 /* NightscoutSettings.swift in Sources */, FEFA5C11299F814A00765C17 /* CoreDataStack.swift in Sources */, 3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */, 3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */, @@ -3193,6 +3211,7 @@ 19E1F7EA29D082ED005C8D20 /* IconConfigProvider.swift in Sources */, 44190F0BBA464D74B857D1FB /* PreferencesEditorRootView.swift in Sources */, E97285ED9B814CD5253C6658 /* AddCarbsDataFlow.swift in Sources */, + 19493A3D2C59987700EC83A7 /* DatabaseModels.swift in Sources */, CE48C86428CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift in Sources */, 38E8755427561E9800975559 /* DataFlow.swift in Sources */, 38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */, @@ -3230,7 +3249,6 @@ BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */, F90692D6274B9A450037068D /* HealthKitStateModel.swift in Sources */, C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */, - FE41E4D429463C660047FD55 /* NightscoutStatistics.swift in Sources */, 38E4453B274E411700EC9A94 /* Disk+VolumeInformation.swift in Sources */, 7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */, 38E44534274E411700EC9A94 /* Disk+InternalHelpers.swift in Sources */, diff --git a/FreeAPS/Sources/APS/APSManager.swift b/FreeAPS/Sources/APS/APSManager.swift index c8f7c09ff7..23cd284681 100644 --- a/FreeAPS/Sources/APS/APSManager.swift +++ b/FreeAPS/Sources/APS/APSManager.swift @@ -3,9 +3,6 @@ import CoreData import Foundation import LoopKit import LoopKitUI -import OmniBLE -import OmniKit -import RileyLinkKit import SwiftDate import SwiftUI import Swinject @@ -1330,26 +1327,11 @@ final class BaseAPSManager: APSManager, Injectable { bolusReporter?.addObserver(self) } - private func updateStatus() { - debug(.apsManager, "force update status") - guard let pump = pumpManager else { - return - } - - if let omnipod = pump as? OmnipodPumpManager { - omnipod.getPodStatus { _ in } - } - if let omnipodBLE = pump as? OmniBLEPumpManager { - omnipodBLE.getPodStatus { _ in } - } - } - private func clearBolusReporter() { bolusReporter?.removeObserver(self) bolusReporter = nil processQueue.asyncAfter(deadline: .now() + 0.5) { self.bolusProgress.send(nil) - self.updateStatus() } } } diff --git a/FreeAPS/Sources/APS/OpenAPS/Constants.swift b/FreeAPS/Sources/APS/OpenAPS/Constants.swift index 3698b2e230..9cba5a742b 100644 --- a/FreeAPS/Sources/APS/OpenAPS/Constants.swift +++ b/FreeAPS/Sources/APS/OpenAPS/Constants.swift @@ -87,10 +87,15 @@ extension OpenAPS { static let uploadedCGMState = "upload/uploaded-cgm-state.json" static let uploadedPodAge = "upload/uploaded-pod-age.json" static let uploadedProfile = "upload/uploaded-profile.json" + static let uploadedProfileToDatabase = "upload/uploaded-profile_database.json" static let uploadedPreferences = "upload/uploaded-preferences.json" static let uploadedSettings = "upload/uploaded-settings.json" static let uploadedManualGlucose = "upload/uploaded-manual-readings.json" static let notUploadedOverrides = "upload/not-uploaded-overrides.json" + static let uploadedPumpSettings = "upload/uploaded-pump_settings.json" + static let uploadedTempTargetsDatabase = "upload/uploaded-temptargets_database.json" + static let uploadedMealPresets = "upload/uploaded-meal-presets.json" + static let uploadedOverridePresets = "upload/uploaded-override-presets.json" } enum FreeAPS { diff --git a/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift b/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift index 388c72c038..9877adc0a2 100644 --- a/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift +++ b/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift @@ -206,4 +206,132 @@ final class CoreDataStorage { } return presetsArray } + + func fetchOnbarding() -> Bool { + var firstRun = true + coredataContext.performAndWait { + let requestBool = Onboarding.fetchRequest() as NSFetchRequest + let sort = NSSortDescriptor(key: "date", ascending: false) + requestBool.sortDescriptors = [sort] + requestBool.fetchLimit = 1 + try? firstRun = self.coredataContext.fetch(requestBool).first?.firstRun ?? true + } + return firstRun + } + + func saveOnbarding() { + coredataContext.performAndWait { [self] in + let save = Onboarding(context: self.coredataContext) + save.firstRun = false + save.date = Date.now + try? self.coredataContext.save() + } + } + + func startOnbarding() { + coredataContext.performAndWait { [self] in + let save = Onboarding(context: self.coredataContext) + save.firstRun = true + save.date = Date.now + try? self.coredataContext.save() + } + } + + func fetchSettingProfileName() -> String { + fetchActiveProfile() + } + + func fetchSettingProfileNames() -> [Profiles]? { + var presetsArray: [Profiles]? + coredataContext.performAndWait { + let requestProfiles = Profiles.fetchRequest() as NSFetchRequest + let sort = NSSortDescriptor(key: "date", ascending: false) + requestProfiles.sortDescriptors = [sort] + try? presetsArray = self.coredataContext.fetch(requestProfiles) + } + return presetsArray + } + + func fetchUniqueSettingProfileName(_ name: String) -> Bool { + var presetsArray: Profiles? + coredataContext.performAndWait { + let requestProfiles = Profiles.fetchRequest() as NSFetchRequest + let sort = NSSortDescriptor(key: "date", ascending: false) + requestProfiles.sortDescriptors = [sort] + requestProfiles.predicate = NSPredicate( + format: "uploaded == true && name == %@", name as String + ) + try? presetsArray = self.coredataContext.fetch(requestProfiles).first + } + return (presetsArray != nil) + } + + func saveProfileSettingName(name: String) { + coredataContext.perform { [self] in + let save = Profiles(context: self.coredataContext) + save.name = name + save.date = Date.now + try? self.coredataContext.save() + } + } + + func migrateProfileSettingName(name: String) { + coredataContext.perform { [self] in + let save = Profiles(context: self.coredataContext) + save.name = name + save.date = Date.now + save.uploaded = true + try? self.coredataContext.save() + } + } + + func profileSettingUploaded(name: String) { + var profile: String = name + if profile.isEmpty { + profile = "default" + } + + // Avoid duplicates + if !fetchUniqueSettingProfileName(name) { + coredataContext.perform { [self] in + let save = Profiles(context: self.coredataContext) + save.name = profile + save.date = Date.now + save.uploaded = true + try? self.coredataContext.save() + } + } + } + + func activeProfile(name: String) { + coredataContext.perform { [self] in + let save = ActiveProfile(context: self.coredataContext) + save.name = name + save.date = Date.now + save.active = true + try? self.coredataContext.save() + } + } + + func checkIfActiveProfile() -> Bool { + var presetsArray = [ActiveProfile]() + coredataContext.performAndWait { + let requestProfiles = ActiveProfile.fetchRequest() as NSFetchRequest + let sort = NSSortDescriptor(key: "date", ascending: false) + requestProfiles.sortDescriptors = [sort] + try? presetsArray = self.coredataContext.fetch(requestProfiles) + } + return (presetsArray.first?.active ?? false) + } + + func fetchActiveProfile() -> String { + var presetsArray = [ActiveProfile]() + coredataContext.performAndWait { + let requestProfiles = ActiveProfile.fetchRequest() as NSFetchRequest + let sort = NSSortDescriptor(key: "date", ascending: false) + requestProfiles.sortDescriptors = [sort] + try? presetsArray = self.coredataContext.fetch(requestProfiles) + } + return presetsArray.first?.name ?? "default" + } } diff --git a/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings index 6e289e9e40..2de9eb192d 100644 --- a/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Other"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2108,7 +2132,7 @@ Enact a temp Basal or a temp target */ "DeltaContactValue" = "Delta"; /* Contact Image, settings, Trend (data to display) */ -"TrendContactValue" = "Trend"; +"TrendContactValue" = "إتجاه"; /* Contact Image, settings, Last loop time (data to display) */ "LastLoopTimeContactValue" = "Last loop time"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings index f8638ff9ee..25a2d8e26f 100644 --- a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Glat Glukose Værdi"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Andet"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Vis Tidsinterval Indstillingsknap"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Lommeregner"; diff --git a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings index 92feae5c2e..6945c414ce 100644 --- a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings @@ -47,7 +47,7 @@ "Done" = "Fertig"; /* Bolus View footer */ -"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Letzte Loop-Schleife vor %@ Minuten. Berechnung des (Mahlzeiten-)Bolus abschließen oder abbrechen, um den nächsten Schleifendurchlauf zu ermöglichen."; /* Calender Option */ "Display Emojis as Labels" = "Emojis als Etiketten benutzen"; @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Glätten der Glukosewerte"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Datenübermittlung"; + +/* Share and Backup page header */ +"Share and Backup" = "Teilen und Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Einstellungen und Statistiken hochladen"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Teilen und sichern Sie alle Ihre Einstellungen und Statistiken"; + +/* Title of dropdown menu */ +"Sex" = "Geschlecht"; + +/* Sex dropdown menu option */ "Woman" = "Weiblich"; -/* */ +/* Sex dropdown menu option */ "Man" = "Männlich"; -/* */ +/* Sex dropdown menu option */ "Other" = "Sonstiges"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Geheim-Schlüssel"; -/* */ +/* Title of birth date field */ "Birth Date" = "Geburtsdatum"; -/* */ -"Share all of your Statistics" = "Alle meine Statistiken teilen"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Nur das absolute Minimum teilen"; + +/* Minimum share toggle */ "Just iAPS version number" = "Nur iAPS Versionsnummer"; -/* */ -"Share Bare Minimum" = "Nur das absolute Minimum teilen"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Alle Informationen, die Sie freigeben möchten, werden anonym hochgeladen. Um doppeltes Hochladen zu verhindern, werden die Daten mit einer eindeutigen, zufälligen Zeichenkette identifiziert, die auf Ihrem Telefon gespeichert wird."; +/* Token section title */ +"Your recovery token" = "Ihr Wiederherstellungs-Token"; -/* */ +/* Token display button */ "Tap to display" = "Tippen, um anzuzeigen"; +/* Token copy button */ +"Long press to copy" = "Zum Kopieren lange drücken"; + +/* Link to statistics button */ +"View Personal Statistics" = "Persönliche Statistiken anzeigen"; + /* */ "Your identifier" = "Ihr Identifikator"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Anzeigeintervall Einstellungs-Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Beim Scrollen nie das kleine Glukosediagramm anzeigen"; + /* Setting title */ "Bolus Calculator" = "Bolus-Rechner"; diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index 4303cfb5ae..391ee8e0e3 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -1948,36 +1948,60 @@ Enact a temp Basal or a temp target */ /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; + +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Other"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; diff --git a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings index c20b936d16..c0064fd928 100644 --- a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings @@ -1941,37 +1941,61 @@ Ajustes predeterminados:\n /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Suavizar valores de glucosa"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Compartir"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Mujer"; -/* */ +/* Sex dropdown menu option */ "Man" = "Hombre"; -/* */ +/* Sex dropdown menu option */ "Other" = "Otro"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Anónimo"; -/* */ +/* Title of birth date field */ "Birth Date" = "Fecha de nacimiento"; -/* */ -"Share all of your Statistics" = "Comparte todas las estadísticas"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Compartir lo mínimo"; + +/* Minimum share toggle */ "Just iAPS version number" = "Sólo el número de versión iAPS"; -/* */ -"Share Bare Minimum" = "Compartir lo mínimo"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Todo registro de información que elija compartir se hace de forma anónima. Para evitar subidas de información duplicadas, los datos se identifican con un identificador único aleatorio guardado en su teléfono."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Pulsa para mostrar"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Su identificador"; @@ -2605,6 +2629,9 @@ Un límite Autosens.máx > 1,5 no es recomendable cuando se utiliza la función /* UI/UX option */ "Display Time Interval Setting Button" = "Botón de intervalo de horas visibles"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Calculador de bolo"; diff --git a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings index 4ce18c8a7e..cd03eaf01e 100644 --- a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Other"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings index b9ce82b1b3..6f645af641 100644 --- a/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Valeur Glycémie lisse"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Partage"; + +/* Share and Backup page header */ +"Share and Backup" = "Partage et sauvegarde"; + +/* Section 1 title */ +"Upload settings and statistics" = "Téléverser vos paramètres et statistiques"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Partager et sauvegarder tous vos paramètres et statistiques"; + +/* Title of dropdown menu */ +"Sex" = "Sexe biologique"; + +/* Sex dropdown menu option */ "Woman" = "Femme"; -/* */ +/* Sex dropdown menu option */ "Man" = "Homme"; -/* */ +/* Sex dropdown menu option */ "Other" = "Autre"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Confidentiel"; -/* */ +/* Title of birth date field */ "Birth Date" = "Date de naissance"; -/* */ -"Share all of your Statistics" = "Partagez vos statistiques"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Partager le minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Juste le numéro de version iAPS"; -/* */ -"Share Bare Minimum" = "Partager le minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Toutes les informations que vous choisissez de partager sont téléchargées de façon anonyme. Pour éviter les doublons, vos données sont identifiées avec une chaîne aléatoire unique enregistrée sur votre téléphone : le jeton de récupération."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Chaque parcelle d'information que vous choisissez de partager et téléverser anonymement. Pour éviter les doublons, les données sont identifiées avec une chaine unique aléatoire enregistrée sur votre téléphone."; +/* Token section title */ +"Your recovery token" = "Votre jeton de récupération"; -/* */ +/* Token display button */ "Tap to display" = "Appuyer pour afficher"; +/* Token copy button */ +"Long press to copy" = "Appuyez longuement pour copier"; + +/* Link to statistics button */ +"View Personal Statistics" = "Consulter les statistiques personnelles"; + /* */ "Your identifier" = "Votre identifiant"; @@ -1997,103 +2021,103 @@ Enact a temp Basal or a temp target */ "Access to contacts - unknown state" = "Accès aux contacts - état inconnu"; /* Contact Image, settings, short description of the feature */ -"A Contact Image can be used to get live updates from iAPS to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from iAPS to your Apple Watch Contact complication and/or your iPhone Contact widget."; +"A Contact Image can be used to get live updates from iAPS to your Apple Watch Contact complication and/or your iPhone Contact widget." = "Une image de contact peut être utilisée pour obtenir des mises à jour en direct d'iAPS vers votre complication de contact Apple Watch et/ou votre widget de contact pour iPhone."; /* Contact Image, settings, main header */ -"Contact Image" = "Contact Image"; +"Contact Image" = "Image de contact"; /* Contact Image, settings, label for the layout picker */ -"Layout" = "Layout"; +"Layout" = "Affichage"; /* Contact Image, settings, single layout name */ -"Single" = "Single"; +"Single" = "Simple"; /* Contact Image, settings, split layout name */ -"Split" = "Split"; +"Split" = "Double"; /* Contact Image, settings, outer ring - don't show */ -"DontShowRing" = "Don't show"; +"DontShowRing" = "Ne pas afficher"; /* Contact Image, settings, outer ring - Loop status */ -"LoopStatusRing" = "Loop status"; +"LoopStatusRing" = "État de la boucle"; /* Contact Image, settings, outer ring - IOB */ -"IOBRing" = "IOB"; +"IOBRing" = "Insuline active (IOB)"; /* Contact Image, settings, outer ring - COB */ -"COBRing" = "COB"; +"COBRing" = "Glucides actifs (COB)"; /* Contact Image, settings, outer ring - IOB+COB */ -"IOB+COBRing" = "IOB+COB"; +"IOB+COBRing" = "Insuline active + Glucides actifs"; /* Contact Image, settings, label for the primary value picker */ -"Primary" = "Primary"; +"Primary" = "Principal"; /* Contact Image, settings, label for the top value picker */ -"Top" = "Top"; +"Top" = "En haut"; /* Contact Image, settings, label for the bottom value picker */ -"Bottom" = "Bottom"; +"Bottom" = "En bas"; /* Contact Image, settings, header for the outer ring section */ -"Ring" = "Ring"; +"Ring" = "Anneau"; /* Contact Image, settings, label for the outer ring picker */ -"Outer" = "Outer"; +"Outer" = "Anneau extérieur"; /* Contact Image, settings, label for the ring width picker */ -"Width" = "Width"; +"Width" = "Largeur"; /* Contact Image, settings, label for the ring gap picker */ -"Gap" = "Gap"; +"Gap" = "Espacement"; /* Contact Image, settings, header for the font section */ -"Font" = "Font"; +"Font" = "Police de caractères"; /* Contact Image, settings, label for the font size picker */ -"Size" = "Size"; +"Size" = "Taille"; /* Contact Image, settings, label for the secondary font size picker */ -"Secondary size" = "Secondary size"; +"Secondary size" = "Taille secondaire"; /* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */ -"Tracking" = "Tracking"; +"Tracking" = "Espacement"; /* Contact Image, settings, label for the font weight picker */ -"Weight" = "Weight"; +"Weight" = "Poids"; /* Contact Image, settings, label for the dark-mode switcher */ -"Dark mode" = "Dark mode"; +"Dark mode" = "Mode sombre"; /* Contact Image, settings, tighter font tracking */ -"TighterFontTracking" = "Tighter"; +"TighterFontTracking" = "Très serré"; /* Contact Image, settings, tight font tracking */ -"TightFontTracking" = "Tight"; +"TightFontTracking" = "Serré"; /* Contact Image, settings, normal font tracking */ "NormalFontTracking" = "Normal"; /* Contact Image, settings, wide font tracking */ -"WideFontTracking" = "Wide"; +"WideFontTracking" = "Large"; /* Contact Image, settings, light font weight */ -"LightFontWeight" = "Light"; +"LightFontWeight" = "Léger"; /* Contact Image, settings, regular font weight */ -"RegularFontWeight" = "Regular"; +"RegularFontWeight" = "Régulier"; /* Contact Image, settings, medium font weight */ -"MediumFontWeight" = "Medium"; +"MediumFontWeight" = "Moyen"; /* Contact Image, settings, semibold font weight */ -"SemiboldFontWeight" = "Semibold"; +"SemiboldFontWeight" = "Semi-gras"; /* Contact Image, settings, bold font weight */ -"BoldFontWeight" = "Bold"; +"BoldFontWeight" = "Gras"; /* Contact Image, settings, black font weight (black is "very bold") */ -"BlackFontWeight" = "Black"; +"BlackFontWeight" = "Noir"; /* Contact Image, settings, don't display any value */ "NoneContactValue" = "Aucun"; @@ -2102,25 +2126,25 @@ Enact a temp Basal or a temp target */ "GlucoseContactValue" = "Glycémie"; /* Contact Image, settings, Eventual BG (data to display) */ -"EventualBGContactValue" = "Eventual BG"; +"EventualBGContactValue" = "Taux de glucose éventuel"; /* Contact Image, settings, Delta (data to display) */ -"DeltaContactValue" = "Delta"; +"DeltaContactValue" = "Écart"; /* Contact Image, settings, Trend (data to display) */ "TrendContactValue" = "Tendance"; /* Contact Image, settings, Last loop time (data to display) */ -"LastLoopTimeContactValue" = "Last loop time"; +"LastLoopTimeContactValue" = "Heure de la dernière boucle"; /* Contact Image, settings, COB (data to display) */ -"COBContactValue" = "COB"; +"COBContactValue" = "Glucides actifs (COB)"; /* Contact Image, settings, IOB (data to display) */ -"IOBContactValue" = "IOB"; +"IOBContactValue" = "Insuline active (IOB)"; /* Contact Image, settings, loop status (data to display) */ -"LoopStatusContactValue" = "Loop status"; +"LoopStatusContactValue" = "État de la boucle"; /* --------------------------------------------------------------------------------------------------------------- Infotexts from openaps.docs and androidaps.docs @@ -2182,10 +2206,10 @@ Enact a temp Basal or a temp target */ "Defaults to false, so that iAPS will set temps whenever it can, so it will be easier to see if the system is working, even when you are offline. This means iAPS will set a “neutral” temp (same as your default basal) if no adjustments are needed. This is an old setting for OpenAPS to have the options to minimise sounds and notifications from the 'rig', that may wake you up during the night." = "La valeur par défaut est faux, de sorte que iAPS définisse une valeur basal dès qu'il le peut, donc il sera plus facile de voir si le système fonctionne, même lorsque vous êtes hors ligne. Cela signifie qu'OpenAPS définira une valeur basale « neutre » (identique à votre basal par défaut) si aucun ajustement n'est nécessaire. Ceci est un ancien paramètre d'OpenAPS afin de minimiser les sons et les notifications de la 'rig', qui pouvaient vous réveiller pendant la nuit. "; /* Headline "Unsuspend If No Temp” */ -"Unsuspend If No Temp" = "Annuler la suspension si aucune température"; +"Unsuspend If No Temp" = "Annuler la suspension à la fin du temporaire zéro"; /* "Unsuspend If No Temp” */ -"Many people occasionally forget to resume / unsuspend their pump after reconnecting it. If you’re one of them, and you are willing to reliably set a zero temp basal whenever suspending and disconnecting your pump, this feature has your back. If enabled, it will automatically resume / unsuspend the pump if you forget to do so before your zero temp expires. As long as the zero temp is still running, it will leave the pump suspended." = "De nombreuses personnes oublient parfois de reprendre / désuspendre leur pompe après la reconnexion. Si vous êtes l'un d'eux, et que vous êtes définissez un basal temporaire à zéro chaque fois que vous suspendez ou débranchez votre pompe, cette fonction peut être pertinente. Si cette option est activée, elle reprendra automatiquement le basal en cours si vous oubliez de reprendre votre pompe. Tant que la valeur temporaire de basal est de zéro, elle laissera la pompe suspendue."; +"Many people occasionally forget to resume / unsuspend their pump after reconnecting it. If you’re one of them, and you are willing to reliably set a zero temp basal whenever suspending and disconnecting your pump, this feature has your back. If enabled, it will automatically resume / unsuspend the pump if you forget to do so before your zero temp expires. As long as the zero temp is still running, it will leave the pump suspended." = "De nombreuses personnes oublient parfois de reprendre / désuspendre leur pompe après la reconnexion. Si vous êtes l'un d'eux, et que vous êtes prêt à définir de manière fiable un basal temporaire zéro à chaque fois que vous suspendez et débranchez votre pompe, cette fonctionnalité peut être pertinente. Si cette option est activée, elle reprendra automatiquement le fonctionnement de votre pompe si vous oubliez de le faire avant l'expiration du basal temporaire à zéro que vous avez programmé. Tant que le basal temporaire à zéro est toujours en cours d'exécution, la pompe restera suspendue."; /* Headline "Enable UAM" */ "Enable UAM" = "Activer UAM"; @@ -2469,7 +2493,7 @@ Enact a temp Basal or a temp target */ "Statistics settings " = "Paramètres des statistiques "; /* UI/UX title */ -"Override HbA1c Unit" = "Outrepasser l'unité HbA1c"; +"Override HbA1c Unit" = "Convertir l'unité HbA1c"; /* UI/UX option */ "In case you're using both profiles and temp targets" = "Si vous utilisez à la fois des profils et des cibles temporaires"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Afficher le bouton de réglage des heures visibles"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Ne jamais afficher le petit graphique de glucose lors du défilement"; + /* Setting title */ "Bolus Calculator" = "Calculateur de Bolus"; diff --git a/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings index c48456d437..e055b06a60 100644 --- a/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Other"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings index 7a73c21a55..4152c9430a 100644 --- a/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Megosztás"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Nő"; -/* */ +/* Sex dropdown menu option */ "Man" = "Férfi"; -/* */ +/* Sex dropdown menu option */ "Other" = "Egyéb"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Titok"; -/* */ +/* Title of birth date field */ "Birth Date" = "Születési dátum"; -/* */ -"Share all of your Statistics" = "Összes statisztika megosztása"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Lehető legkevesebb adat megosztása"; + +/* Minimum share toggle */ "Just iAPS version number" = "Csak az iAPS verzió száma"; -/* */ -"Share Bare Minimum" = "Lehető legkevesebb adat megosztása"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Minden egyes megosztani kívánt információ névtelenül kerül feltöltésre. A duplikált feltöltések elkerülése érdekében az adatokat egy egyedi, véletlenszerű karakterlánc azonosítja, amelyet a telefonján tárolunk."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Érintsd meg a megjelenítéshez"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Az ön azonosítója"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Soha ne jelenítse meg a kis glükózdiagramot görgetés közben"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings index bf8dee5a10..5ec32b4c9c 100644 --- a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Valori Glicemici Levigati"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Condivisione"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Donna"; -/* */ +/* Sex dropdown menu option */ "Man" = "Uomo"; -/* */ +/* Sex dropdown menu option */ "Other" = "Altro"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Segreto"; -/* */ +/* Title of birth date field */ "Birth Date" = "Data di nascita"; -/* */ -"Share all of your Statistics" = "Condividi tutte le tue statistiche"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Condividi Minimo Nullo"; + +/* Minimum share toggle */ "Just iAPS version number" = "Solo il numero di versione iAPS"; -/* */ -"Share Bare Minimum" = "Condividi Minimo Nullo"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Ogni bit di informazioni che si sceglie di condividere viene caricato in modo anonimo. Per evitare duplicati di caricamenti, i dati sono identificati con una stringa casuale univoca salvata sul telefono."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tocca per visualizzare"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Il tuo identificativo"; @@ -2489,6 +2513,9 @@ Per gli utenti più giovani si consiglia di iniziare con un dosaggio ancora più /* UI/UX option */ "Display Time Interval Setting Button" = "Mostra Pulsante Impostazione nell'intervallo di tempo"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Non visualizzare mai il grafico piccolo del glucosio durante lo scorrimento"; + /* Setting title */ "Bolus Calculator" = "Calcolatore Bolo"; diff --git a/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings index 99b5feff9a..e3f454b286 100644 --- a/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Utjevning av blodsukkerverdi"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Annet"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Vis knapp for tidsintervall"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Boluskalkulator"; diff --git a/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings index ca07bd3a48..b60f7ab111 100644 --- a/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings @@ -260,7 +260,7 @@ "Autotune" = "Autotune"; /* */ -"Basal profile" = "Basaal profiel"; +"Basal profile" = "Basaalprofiel"; /* */ "Carb ratio" = "Koolhydraat verhouding"; @@ -284,7 +284,7 @@ "Add" = "Toevoegen"; /* */ -"Basal Profile" = "Basaal profiel"; +"Basal Profile" = "Basaalprofiel"; /* Rate basal profile */ "Rate" = "Waarde"; @@ -363,7 +363,7 @@ Enact a temp Basal or a temp target */ "Invalid URL" = "Ongeldige URL"; /* */ -"Local glucose source" = "Lokale glucose bron"; +"Local glucose source" = "Lokale glucosebron"; /* Header */ "Nightscout Config" = "Nightscout instellingen"; @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Vloeiende glucosewaarde"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Delen van statistieken"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Vrouw"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Anders"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Geheim"; -/* */ +/* Title of birth date field */ "Birth Date" = "Geboortedatum"; -/* */ -"Share all of your Statistics" = "Deel al je statistieken"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Deel alleen het minimale"; + +/* Minimum share toggle */ "Just iAPS version number" = "Alleen iAPS versienummer"; -/* */ -"Share Bare Minimum" = "Deel alleen het minimale"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Alle informatie die je wilt delen, wordt anoniem geüpload. Om dubbele uploads te voorkomen, worden de gegevens geïdentificeerd met een unieke willekeurige tekenreeks die op je apparaat is opgeslagen."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tik om weer te geven"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Jouw identificatie"; @@ -2492,6 +2516,9 @@ Eenvoudig gezegd, de Dynamische Carb Ratio past de koolhydraatverhouding aan op /* UI/UX option */ "Display Time Interval Setting Button" = "Knop voor tijdinterval weergeven"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Laat de kleine glucosegrafiek nooit zien tijdens het scrollen"; + /* Setting title */ "Bolus Calculator" = "Bolus calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings index cb7d4e4518..7d64893723 100644 --- a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings @@ -1932,37 +1932,61 @@ Połączono z Nightscout!"; /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Other"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2490,6 +2514,9 @@ Połączono z Nightscout!"; /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings index 6b3b8144ba..ced78bc51d 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Outros"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings index bfc774aa82..a00bbc4143 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Outro"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings index 1cd9008798..04258d9310 100644 --- a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Сглаживать значения глюкозы"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Делиться статистикой"; + +/* Share and Backup page header */ +"Share and Backup" = "Общий доступ и резервирование"; + +/* Section 1 title */ +"Upload settings and statistics" = "Выгружать настройки и статистику"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Делиться и резервно копировать все настройки и статистику"; + +/* Title of dropdown menu */ +"Sex" = "Пол"; + +/* Sex dropdown menu option */ "Woman" = "Женский"; -/* */ +/* Sex dropdown menu option */ "Man" = "Мужской"; -/* */ +/* Sex dropdown menu option */ "Other" = "Прочее"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Скрыть"; -/* */ +/* Title of birth date field */ "Birth Date" = "День рождения"; -/* */ -"Share all of your Statistics" = "Делиться всей Вашей статистикой"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Минимально делиться"; + +/* Minimum share toggle */ "Just iAPS version number" = "Только номер версии iAPS"; -/* */ -"Share Bare Minimum" = "Минимально делиться"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Любая информация, которой вы хотите поделиться, загружается анонимно. Чтобы предотвратить повторную загрузку, данные идентифицируются с помощью уникальной случайной строки, сохраненной на вашем телефоне - токена восстановления."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Любая информация, которой вы хотите поделиться, загружается анонимно. Чтобы предотвратить повторную загрузку, данные идентифицируются с помощью уникальной случайной строки, сохраненной на вашем телефоне."; +/* Token section title */ +"Your recovery token" = "Ваш токен восстановления"; -/* */ +/* Token display button */ "Tap to display" = "Нажать для отображения"; +/* Token copy button */ +"Long press to copy" = "Длительно нажмите, чтобы скопировать"; + +/* Link to statistics button */ +"View Personal Statistics" = "Просмотр личной статистики"; + /* */ "Your identifier" = "Ваш идентификатор"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Отображать кнопку выбора интервала времени"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Не отображать график уровня глюкозы при прокрутке"; + /* Setting title */ "Bolus Calculator" = "Калькулятор болюса"; diff --git a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings index f0cf23ff3f..14fc01b649 100644 --- a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Hladká hodnota glukózy"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Žena"; -/* */ +/* Sex dropdown menu option */ "Man" = "Muž"; -/* */ +/* Sex dropdown menu option */ "Other" = "Iné"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Tajomstvo"; -/* */ +/* Title of birth date field */ "Birth Date" = "Dátum narodenia"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Len číslo verzie iAPS"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Tlačidlo nastavenia časového intervalu zobrazenia"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Kalkulátor bolusu"; diff --git a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings index 04cf5a4453..2d91a3d5f8 100644 --- a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings @@ -1942,37 +1942,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Utjämna blodsocker från sensor"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Delning"; + +/* Share and Backup page header */ +"Share and Backup" = "Dela och säkerhetskopiera"; + +/* Section 1 title */ +"Upload settings and statistics" = "Ladda upp inställningar och statistik"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Dela och säkerhetskopiera alla dina inställningar och statistik"; + +/* Title of dropdown menu */ +"Sex" = "Kön"; + +/* Sex dropdown menu option */ "Woman" = "Kvinna"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Annan"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Hemligt"; -/* */ +/* Title of birth date field */ "Birth Date" = "Födelsedatum"; -/* */ -"Share all of your Statistics" = "Dela all statistik"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nOm du aktiverar \"Dela och säkerhetskopiera\" kommer dagliga säkerhetskopior av dina inställningar och statistik att göras till en moln-databas.\n\nSe till att kopiera och spara din återställningstoken nedan. Den kommer att behövas om du någon gång behöver importera dina inställningar till en annan telefon."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Dela endast ett minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Endast appversion"; -/* */ -"Share Bare Minimum" = "Dela endast ett minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "All information du väljer att dela laddas upp anonymt. För att förhindra dubbletter av uppladdningar identifieras datan med en unik slumpmässig sträng som sparas på din telefon."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "All information du väljer att dela laddas upp anonymt. För att förhindra dubbletter av uppladdningar identifieras datan med en unik slumpmässig sträng som sparas på telefonen."; +/* Token section title */ +"Your recovery token" = "Din återställningsnyckel"; -/* */ +/* Token display button */ "Tap to display" = "Tryck för att visa"; +/* Token copy button */ +"Long press to copy" = "Tryck länge för att kopiera"; + +/* Link to statistics button */ +"View Personal Statistics" = "Visa personlig statistik"; + /* */ "Your identifier" = "Din identifierare"; @@ -2500,6 +2524,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Visa knapp för tidsintervell"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Dölj det lilla glukosdiagrammet när du scrollar"; + /* Setting title */ "Bolus Calculator" = "Boluskalkylator"; diff --git a/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings index fdadba26d2..a771697a70 100644 --- a/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "Diğer"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings index bdce32793e..a711c77358 100644 --- a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Гладке Значення Глюкози"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Поширити"; + +/* Share and Backup page header */ +"Share and Backup" = "Поділитися та створити Резервну копію"; + +/* Section 1 title */ +"Upload settings and statistics" = "Звантажити налаштування та статистику"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Поділитись та зробити Резервну копію всіх ваших Налаштувань та Статистики"; + +/* Title of dropdown menu */ +"Sex" = "Стать"; + +/* Sex dropdown menu option */ "Woman" = "Жінка"; -/* */ +/* Sex dropdown menu option */ "Man" = "Чоловік"; -/* */ +/* Sex dropdown menu option */ "Other" = "Інше"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Секретний ключ"; -/* */ +/* Title of birth date field */ "Birth Date" = "Дата народження"; -/* */ -"Share all of your Statistics" = "Поділіться своєю Статистикою"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Мінімум розподілу"; + +/* Minimum share toggle */ "Just iAPS version number" = "Номер версії iAPS"; -/* */ -"Share Bare Minimum" = "Мінімум розподілу"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Кожна інформація, якою ви вирішите поділитися, завантажується анонімно. Щоб запобігти повторним завантаженням, дані ідентифікуються за допомогою унікального випадкового рядка, збереженого на вашому телефоні, токена відновлення."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Кожна інформація, якою ви вирішите поділитися, завантажується анонімно. Щоб запобігти повторним завантаженням, дані ідентифікуються за допомогою унікального випадкового рядка, збереженого на вашому телефоні."; +/* Token section title */ +"Your recovery token" = "Ваш токен для відновлення"; -/* */ +/* Token display button */ "Tap to display" = "Натисніть для відображення"; +/* Token copy button */ +"Long press to copy" = "Утримуйте для копіювання"; + +/* Link to statistics button */ +"View Personal Statistics" = "Переглянути Персональну Статистику"; + /* */ "Your identifier" = "Ваш ідентифікатор"; @@ -2488,6 +2512,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Відображення Кнопки Налаштування Інтервалу Часу"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Ніколи не відображайте маленьку діаграму рівня глюкози під час прокручування"; + /* Setting title */ "Bolus Calculator" = "Калькулятор Болюса"; diff --git a/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings index 9fa75840ae..b69fb59dc6 100644 --- a/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings @@ -348,7 +348,7 @@ Enact a temp Basal or a temp target */ "Allow uploads" = "Cho phép tải lên"; /* API secret in NS */ -"API secret" = "Khóa API"; +"API secret" = "API secret"; /* Connect to NS */ "Connect" = "Kết nối"; @@ -414,22 +414,22 @@ Enact a temp Basal or a temp target */ "\nSettings were imported but the Basals couldn't be saved to pump (No pump). Check your basal settings and tap ´Save on Pump´ to sync the new basal settings" = "\n Các cấu hình đã được nhập nhưng liều nền không được lưu vào bơm (không có bơm). Kiểm tra cấu hình liều nền của bạn và nhấn 'Lưu vào bơm' để đồng hóa cấu hình liều nền mới"; /* Import Error Headline */ -"Import Error" = "Cập nhật lỗi"; +"Import Error" = "Nhập lỗi"; /* */ -"Yes, Import" = "Đồng ý, hãy cập nhật"; +"Yes, Import" = "Đồng ý, hãy nhập"; /* */ "Import settings from Nightscout" = "Nhập cấu hình từ Nightscout"; /* */ -"Import settings?" = "Nhập các cấu hình?"; +"Import settings?" = "Nhập các cài đặt?"; /* */ "Import from Nightscout" = "Nhập từ Nightscout"; /* */ -"Settings imported" = "Các cấu hình đã được nhập"; +"Settings imported" = "Các cài đặt đã được nhập"; /* Import Error */ "\nMismatching glucose units in Nightscout and Pump Settings. Import settings aborted." = "\n Đơn vị glucose không khớp trong Cài đặt Nightscout và Pump. Nhập cấu hình bị hủy bỏ."; @@ -513,13 +513,13 @@ Enact a temp Basal or a temp target */ "0 U/hr" = "U/hr"; /* abbreviation for days */ -"d" = "ngày"; +"d" = "d"; /* abbreviation for hours */ -"h" = "giờ"; +"h" = "h"; /* abbreviation for minutes */ -"m" = "phút"; +"m" = "m"; /* */ "Closed loop" = "Vòng lặp kín"; @@ -1194,7 +1194,7 @@ Enact a temp Basal or a temp target */ "Animated Background" = "Nền màn hình động"; /* Sensor day(s) */ -" day(s)" = " ngày(s)"; +" day(s)" = " day(s)"; /* Option to show HR in Watch app*/ "Display HR on Watch" = "Hiển thị trên đồng hồ"; @@ -1724,10 +1724,10 @@ Enact a temp Basal or a temp target */ "Total Delivery" = "Tổng liều"; /* */ -"Add Omnipod Dash" = "Thay Omnipod Dash"; +"Add Omnipod Dash" = "Thêm Omnipod Dash"; /* */ -"Insert Cannula" = "Thay Cannula"; +"Insert Cannula" = "Lắp Cannula"; /* */ "Check Cannula" = "Kiểm tra Cannula"; @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Làm mịn Glucose"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Chia sẻ"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Nữ"; -/* */ +/* Sex dropdown menu option */ "Man" = "Nam"; -/* */ +/* Sex dropdown menu option */ "Other" = "Khác"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Bí mật"; -/* */ +/* Title of birth date field */ "Birth Date" = "Ngày sinh"; -/* */ -"Share all of your Statistics" = "Chia sẻ tất cả số liệu thống kê của bạn"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Chia sẻ ở mức tối thiểu"; + +/* Minimum share toggle */ "Just iAPS version number" = "Chỉ phiên bản iAPS"; -/* */ -"Share Bare Minimum" = "Chia sẻ ở mức tối thiểu"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Mọi thông tin bạn chọn chia sẻ đều được tải lên ẩn danh. Để tránh tải lên trùng lặp, dữ liệu được xác định bằng một chuỗi ngẫu nhiên duy nhất được lưu trên điện thoại của bạn."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Chạm để hiển thị"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Mã định danh của bạn"; @@ -2075,25 +2099,25 @@ Enact a temp Basal or a temp target */ "NormalFontTracking" = "Normal"; /* Contact Image, settings, wide font tracking */ -"WideFontTracking" = "Rộng"; +"WideFontTracking" = "Wide"; /* Contact Image, settings, light font weight */ -"LightFontWeight" = "Phông nền sáng"; +"LightFontWeight" = "Light"; /* Contact Image, settings, regular font weight */ -"RegularFontWeight" = "Binh thường"; +"RegularFontWeight" = "Regular"; /* Contact Image, settings, medium font weight */ -"MediumFontWeight" = "Trung bình"; +"MediumFontWeight" = "Medium"; /* Contact Image, settings, semibold font weight */ -"SemiboldFontWeight" = "Đậm nhẹ"; +"SemiboldFontWeight" = "Semibold"; /* Contact Image, settings, bold font weight */ -"BoldFontWeight" = "Đậm"; +"BoldFontWeight" = "Bold"; /* Contact Image, settings, black font weight (black is "very bold") */ -"BlackFontWeight" = "Đen"; +"BlackFontWeight" = "Black"; /* Contact Image, settings, don't display any value */ "NoneContactValue" = "Không"; @@ -2131,7 +2155,7 @@ Enact a temp Basal or a temp target */ "Rewind Resets Autosens" = "Rewind sẽ thiết lập lại Autosens"; /* ”Rewind Resets Autosens” */ -"This feature, enabled by default, resets the autosens ratio to neutral when you rewind your pump, on the assumption that this corresponds to a probable site change. Autosens will begin learning sensitivity anew from the time of the rewind, which may take up to 6 hours. If you usually rewind your pump independently of site changes, you may want to consider disabling this feature." = "Tính năng này, được bật theo mặc định, sẽ đặt lại tỷ lệ cảm biến tự động về mức trung tính khi bạn tua lại máy bơm của mình, với giả định rằng điều này tương ứng với một sự thay đổi địa điểm có thể xảy ra. Autosens sẽ bắt đầu học lại độ nhạy kể từ thời điểm tua lại, quá trình này có thể mất tới 6 giờ. Nếu bạn thường tua lại máy bơm của mình một cách độc lập với những thay đổi ở địa điểm, bạn có thể cân nhắc việc tắt tính năng này."; +"This feature, enabled by default, resets the autosens ratio to neutral when you rewind your pump, on the assumption that this corresponds to a probable site change. Autosens will begin learning sensitivity anew from the time of the rewind, which may take up to 6 hours. If you usually rewind your pump independently of site changes, you may want to consider disabling this feature." = "Tính năng này, được bật theo mặc định, sẽ đặt lại tỷ lệ cảm biến tự động về mức trung tính khi bạn tua lại máy bơm của mình, với giả định rằng điều này tương ứng với một sự thay đổi điểm gắn có thể xảy ra. Autosens sẽ bắt đầu học lại độ nhạy kể từ thời điểm tua lại, quá trình này có thể mất tới 6 giờ. Nếu bạn thường tua lại máy bơm của mình một cách độc lập với những thay đổi ở địa điểm, bạn có thể cân nhắc việc tắt tính năng này."; /* Headline "High Temptarget Raises Sensitivity" */ "High Temptarget Raises Sensitivity" = "Mục tiêu tạm thời cao sẽ làm tăng độ nhạy"; @@ -2179,13 +2203,13 @@ Enact a temp Basal or a temp target */ "Skip Neutral Temps" = "Bỏ qua liều tạm thời trung lập"; /* "Skip Neutral Temps" */ -"Defaults to false, so that iAPS will set temps whenever it can, so it will be easier to see if the system is working, even when you are offline. This means iAPS will set a “neutral” temp (same as your default basal) if no adjustments are needed. This is an old setting for OpenAPS to have the options to minimise sounds and notifications from the 'rig', that may wake you up during the night." = "Mặc định là False, do đó iAPS sẽ đặt liều tạm thời bất cứ khi nào có thể, do đó, sẽ dễ dàng hơn để xem hệ thống có hoạt động hay không, ngay cả khi bạn ngoại tuyến. Điều này có nghĩa là OpenAPS sẽ đặt Liều tạm thời “trung tính” (giống như Liều tạm thời cơ bản mặc định của bạn) nếu không cần điều chỉnh. Đây là cài đặt cũ để OpenAPS có các tùy chọn giảm thiểu âm thanh và thông báo từ 'âm mưu' có thể đánh thức bạn vào ban đêm. "; +"Defaults to false, so that iAPS will set temps whenever it can, so it will be easier to see if the system is working, even when you are offline. This means iAPS will set a “neutral” temp (same as your default basal) if no adjustments are needed. This is an old setting for OpenAPS to have the options to minimise sounds and notifications from the 'rig', that may wake you up during the night." = "Mặc định là False, do đó iAPS sẽ đặt liều tạm thời bất cứ khi nào có thể, do đó, sẽ dễ dàng hơn để xem hệ thống có hoạt động hay không, ngay cả khi bạn ngoại tuyến. Điều này có nghĩa là OpenAPS sẽ đặt Liều tạm thời “trung tính” (giống như Liều tạm thời cơ bản mặc định của bạn) nếu không cần điều chỉnh. Đây là cài đặt cũ để OpenAPS có các tùy chọn giảm thiểu âm thanh và thông báo từ 'rig' có thể đánh thức bạn vào ban đêm. "; /* Headline "Unsuspend If No Temp” */ "Unsuspend If No Temp" = "Bỏ tạm dừng nếu không có tạm thời"; /* "Unsuspend If No Temp” */ -"Many people occasionally forget to resume / unsuspend their pump after reconnecting it. If you’re one of them, and you are willing to reliably set a zero temp basal whenever suspending and disconnecting your pump, this feature has your back. If enabled, it will automatically resume / unsuspend the pump if you forget to do so before your zero temp expires. As long as the zero temp is still running, it will leave the pump suspended." = "Nhiều người thỉnh thoảng quên tiếp tục/hủy tạm dừng máy bơm sau khi kết nối lại. Nếu bạn là một trong số họ và sẵn sàng đặt nhiệt độ cơ bản bằng 0 một cách đáng tin cậy bất cứ khi nào tạm dừng và ngắt kết nối máy bơm của mình, thì tính năng này sẽ hỗ trợ bạn. Nếu được bật, nó sẽ tự động tiếp tục/hủy tạm dừng máy bơm nếu bạn quên làm như vậy trước khi hết tạm thời 0. Miễn là tạm thời bằng 0 vẫn đang chạy, nó sẽ khiến máy bơm bị treo."; +"Many people occasionally forget to resume / unsuspend their pump after reconnecting it. If you’re one of them, and you are willing to reliably set a zero temp basal whenever suspending and disconnecting your pump, this feature has your back. If enabled, it will automatically resume / unsuspend the pump if you forget to do so before your zero temp expires. As long as the zero temp is still running, it will leave the pump suspended." = "Nhiều người thỉnh thoảng quên tiếp tục/hủy tạm dừng máy bơm sau khi kết nối lại. Nếu bạn là một trong số họ và sẵn sàng đặt liều tạm thời cơ bản bằng 0 một cách đáng tin cậy bất cứ khi nào tạm dừng và ngắt kết nối máy bơm của mình, thì tính năng này sẽ hỗ trợ bạn. Nếu được bật, nó sẽ tự động tiếp tục/hủy tạm dừng máy bơm nếu bạn quên làm như vậy trước khi hết tạm thời 0. Miễn là tạm thời bằng 0 vẫn đang chạy, nó sẽ khiến máy bơm bị treo."; /* Headline "Enable UAM" */ "Enable UAM" = "Kích hoạt UAM"; @@ -2203,7 +2227,7 @@ Enact a temp Basal or a temp target */ "Enable SMB With Temptarget" = "Kích hoạt SMB với mục tiêu tạm thời"; /* "Enable SMB With Temptarget” */ -"This enables supermicrobolus (SMB) with eating soon / low temp targets. With this feature enabled, any temporary target below 100mg/dL, such as a temp target of 99 (or 80, the typical eating soon target) will enable SMB." = "Điều này cho phép Super Micro Bolus (SMB) đạt được mục tiêu ăn sớm / nhiệt độ thấp. Khi tính năng này được bật, mọi mục tiêu tạm thời dưới 100mg/dL, chẳng hạn như mục tiêu tạm thời là 99 (hoặc 80, mục tiêu ăn sớm thông thường) sẽ kích hoạt SMB."; +"This enables supermicrobolus (SMB) with eating soon / low temp targets. With this feature enabled, any temporary target below 100mg/dL, such as a temp target of 99 (or 80, the typical eating soon target) will enable SMB." = "Điều này cho phép Super Micro Bolus (SMB) đạt được mục tiêu ăn sớm / liều tạm thời thấp. Khi tính năng này được bật, mọi mục tiêu tạm thời dưới 100mg/dL, chẳng hạn như mục tiêu tạm thời là 99 (hoặc 80, mục tiêu ăn sớm thông thường) sẽ kích hoạt SMB."; /* Headline "Enable SMB Always" */ "Enable SMB Always" = "Luôn bật SMB"; @@ -2321,7 +2345,7 @@ Enact a temp Basal or a temp target */ "Max UAM SMB Basal Minutes" = "Số phút cơ bản UAM SMB tối đa"; /* "Max UAM SMB Basal Minutes" */ -"Defaults to start at 30. This is the maximum minutes of basal that can be delivered by UAM as a single SMB when IOB exceeds COB. This gives the ability to make UAM more or less aggressive if you choose. It is recommended that the value is set to start at 30, in line with the default, and if you choose to increase this value, do so in no more than 15 minute increments, keeping a close eye on the effects of the changes. Reducing the value will cause UAM to dose less insulin for each SMB. It is not recommended to set this value higher than 60 mins, as this may affect the ability for the algorithm to safely zero temp. It is also recommended that pushover is used when setting the value to be greater than default, so that alerts are generated for any predicted lows or highs." = "Mặc định bắt đầu từ 30. Đây là số phút cơ bản tối đa mà UAM có thể phân phối dưới dạng một SMB khi IOB vượt quá COB. Điều này mang lại khả năng khiến UAM trở nên hung hãn hơn hoặc ít hơn nếu bạn chọn. Bạn nên đặt giá trị này để bắt đầu ở mức 30, phù hợp với giá trị mặc định và nếu bạn chọn tăng giá trị này, hãy thực hiện với khoảng tăng không quá 15 phút, đồng thời theo dõi chặt chẽ tác động của các thay đổi. Việc giảm giá trị sẽ khiến UAM giảm liều insulin cho mỗi SMB. Không nên đặt giá trị này cao hơn 60 phút vì điều này có thể ảnh hưởng đến khả năng thuật toán về nhiệt độ bằng 0 một cách an toàn. Chúng tôi cũng khuyên bạn nên sử dụng tính năng đẩy khi đặt giá trị lớn hơn giá trị mặc định để cảnh báo được tạo ra cho bất kỳ mức thấp hoặc mức cao được dự đoán nào."; +"Defaults to start at 30. This is the maximum minutes of basal that can be delivered by UAM as a single SMB when IOB exceeds COB. This gives the ability to make UAM more or less aggressive if you choose. It is recommended that the value is set to start at 30, in line with the default, and if you choose to increase this value, do so in no more than 15 minute increments, keeping a close eye on the effects of the changes. Reducing the value will cause UAM to dose less insulin for each SMB. It is not recommended to set this value higher than 60 mins, as this may affect the ability for the algorithm to safely zero temp. It is also recommended that pushover is used when setting the value to be greater than default, so that alerts are generated for any predicted lows or highs." = "Mặc định bắt đầu từ 30. Đây là số phút cơ bản tối đa mà UAM có thể phân phối dưới dạng một SMB khi IOB vượt quá COB. Điều này mang lại khả năng khiến UAM trở nên hung hãn hơn hoặc ít hơn nếu bạn chọn. Bạn nên đặt giá trị này để bắt đầu ở mức 30, phù hợp với giá trị mặc định và nếu bạn chọn tăng giá trị này, hãy thực hiện với khoảng tăng không quá 15 phút, đồng thời theo dõi chặt chẽ tác động của các thay đổi. Việc giảm giá trị sẽ khiến UAM giảm liều insulin cho mỗi SMB. Không nên đặt giá trị này cao hơn 60 phút vì điều này có thể ảnh hưởng đến khả năng thuật toán về liều tạm thời bằng 0 một cách an toàn. Chúng tôi cũng khuyên bạn nên sử dụng tính năng đẩy khi đặt giá trị lớn hơn giá trị mặc định để cảnh báo được tạo ra cho bất kỳ mức thấp hoặc mức cao được dự đoán nào."; /* Headline "SMB Interval" */ "SMB Interval" = "Khoảng thời gian SMB"; @@ -2330,7 +2354,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes for new SMB since last SMB or manual bolus" = "Thời lượng tối thiểu tính bằng phút cho SMB mới kể từ SMB cuối cùng hoặc liều truyền thủ công"; /* Headline "Bolus Increment" */ -"Bolus Increment" = "Tăng liều Bolus nhanh"; +"Bolus Increment" = "Tốc độ Bolus nhanh"; /* "Bolus Increment" */ "Smallest enacted SMB amount. Minimum amount for Omnipod pumps is 0.05 U, whereas for Medtronic pumps it differs for various models, from 0.025 U to 0.10 U. Please check the minimum bolus amount which can be delivered by your pump. The default value is 0.1." = "Số SMB được ban hành nhỏ nhất. Lượng tối thiểu đối với máy bơm Omnipod là 0,05 U, trong khi đối với máy bơm Medtronic, lượng này khác nhau đối với nhiều kiểu máy khác nhau, từ 0,025 U đến 0,10 U. Vui lòng kiểm tra lượng truyền nhanh tối thiểu mà máy bơm của bạn có thể cung cấp. Giá trị mặc định là 0,1."; @@ -2489,6 +2513,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Nút cài đặt khoảng thời gian hiển thị"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Không bao giờ hiển thị biểu đồ glucose nhỏ khi cuộn"; + /* Setting title */ "Bolus Calculator" = "Tính toán Bolus"; @@ -2499,7 +2526,7 @@ Enact a temp Basal or a temp target */ "Live Activity" = "Hoạt động trực tiếp"; /* Notification option */ -"Live activity displays blood glucose live on the lock screen and on the dynamic island (if available)" = "Hiển thị hoạt động trực tiếp đường huyết trên màn hình khóa"; +"Live activity displays blood glucose live on the lock screen and on the dynamic island (if available)" = "Hiển thị hoạt động trực tiếp đường huyết trên màn hình khóa và năng động (nếu có)"; /* Notification option */ "Show Live activity" = "Hiển thị hoạt động trực tiếp"; diff --git a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings index 851acff552..c2bc5e9730 100644 --- a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings @@ -1930,37 +1930,61 @@ Enact a temp Basal or a temp target */ /* Smoothing of CGM readings */ "Smooth Glucose Value" = "Smooth Glucose Value"; /* ------------------------------------------- Sharing -------------------------------------------------------*/ -/* */ +/* Setting Title */ + +"Sharing" = "Sharing"; + +/* Share and Backup page header */ +"Share and Backup" = "Share and Backup"; + +/* Section 1 title */ +"Upload settings and statistics" = "Upload settings and statistics"; +/* On-off toggle */ +"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; + +/* Title of dropdown menu */ +"Sex" = "Sex"; + +/* Sex dropdown menu option */ "Woman" = "Woman"; -/* */ +/* Sex dropdown menu option */ "Man" = "Man"; -/* */ +/* Sex dropdown menu option */ "Other" = "其他"; -/* */ +/* Sex dropdown menu option */ "Secret" = "Secret"; -/* */ +/* Title of birth date field */ "Birth Date" = "Birth Date"; -/* */ -"Share all of your Statistics" = "Share all of your Statistics"; +/* Share and Backup info text */ +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; -/* */ +/* Section title if sharing off */ +"Share Bare Minimum" = "Share Bare Minimum"; + +/* Minimum share toggle */ "Just iAPS version number" = "Just iAPS version number"; -/* */ -"Share Bare Minimum" = "Share Bare Minimum"; +/* Share info text */ +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; -/* */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone."; +/* Token section title */ +"Your recovery token" = "Your recovery token"; -/* */ +/* Token display button */ "Tap to display" = "Tap to display"; +/* Token copy button */ +"Long press to copy" = "Long press to copy"; + +/* Link to statistics button */ +"View Personal Statistics" = "View Personal Statistics"; + /* */ "Your identifier" = "Your identifier"; @@ -2490,6 +2514,9 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Display Time Interval Setting Button" = "Display Time Interval Setting Button"; +/* UI/UX option */ +"Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Models/BloodGlucose.swift b/FreeAPS/Sources/Models/BloodGlucose.swift index ad4307f40c..9f37b8ae40 100644 --- a/FreeAPS/Sources/Models/BloodGlucose.swift +++ b/FreeAPS/Sources/Models/BloodGlucose.swift @@ -1,6 +1,6 @@ import Foundation -struct BloodGlucose: JSON, Identifiable, Hashable { +struct BloodGlucose: JSON, Identifiable, Hashable, Codable { enum Direction: String, JSON { case tripleUp = "TripleUp" case doubleUp = "DoubleUp" @@ -16,6 +16,76 @@ struct BloodGlucose: JSON, Identifiable, Hashable { case rateOutOfRange = "RATE OUT OF RANGE" } + enum CodingKeys: String, CodingKey { + case _id + case sgv + case direction + case date + case dateString + case unfiltered + case filtered + case noise + case glucose + case type + case activationDate + case sessionStartDate + case transmitterID + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + _id = try container.decode(String.self, forKey: ._id) + + do { + sgv = try container.decode(Int.self, forKey: .sgv) + } catch { + // The nightscout API returns a double instead of an int + sgv = Int(try container.decode(Double.self, forKey: .sgv)) + } + + direction = try container.decodeIfPresent(Direction.self, forKey: .direction) + date = try container.decode(Decimal.self, forKey: .date) + dateString = try container.decode(Date.self, forKey: .dateString) + unfiltered = try container.decodeIfPresent(Decimal.self, forKey: .unfiltered) + filtered = try container.decodeIfPresent(Decimal.self, forKey: .filtered) + noise = try container.decodeIfPresent(Int.self, forKey: .noise) + glucose = try container.decodeIfPresent(Int.self, forKey: .glucose) + type = try container.decodeIfPresent(String.self, forKey: .type) + activationDate = try container.decodeIfPresent(Date.self, forKey: .activationDate) + sessionStartDate = try container.decodeIfPresent(Date.self, forKey: .sessionStartDate) + transmitterID = try container.decodeIfPresent(String.self, forKey: .transmitterID) + } + + init( + _id: String = UUID().uuidString, + sgv: Int? = nil, + direction: Direction? = nil, + date: Decimal, + dateString: Date, + unfiltered: Decimal? = nil, + filtered: Decimal? = nil, + noise: Int? = nil, + glucose: Int? = nil, + type: String? = nil, + activationDate: Date? = nil, + sessionStartDate: Date? = nil, + transmitterID: String? = nil + ) { + self._id = _id + self.sgv = sgv + self.direction = direction + self.date = date + self.dateString = dateString + self.unfiltered = unfiltered + self.filtered = filtered + self.noise = noise + self.glucose = glucose + self.type = type + self.activationDate = activationDate + self.sessionStartDate = sessionStartDate + self.transmitterID = transmitterID + } + var _id = UUID().uuidString var id: String { _id diff --git a/FreeAPS/Sources/Models/DatabaseModels.swift b/FreeAPS/Sources/Models/DatabaseModels.swift new file mode 100644 index 0000000000..90108ec53e --- /dev/null +++ b/FreeAPS/Sources/Models/DatabaseModels.swift @@ -0,0 +1,100 @@ +import Foundation + +struct DatabasePumpSettings: JSON { + var report = "pumpSettings" + let settings: PumpSettings? + let enteredBy: String + let profile: String? +} + +struct DatabaseTempTargets: JSON { + var report = "tempTargets" + let tempTargets: [TempTarget] + let enteredBy: String + let profile: String? +} + +struct DatabaseProfileStore: JSON { + var report = "profiles" + let units: String + var enteredBy: String + let store: [String: ScheduledNightscoutProfile] + var profile: String +} + +struct NightscoutStatistics: JSON { + var report = "statistics" + let dailystats: Statistics? + let justVersion: BareMinimum? +} + +struct NightscoutPreferences: JSON { + var report = "preferences" + let preferences: Preferences? + let enteredBy: String + let profile: String? +} + +struct NightscoutSettings: JSON { + var report = "settings" + let settings: FreeAPSSettings? + let enteredBy: String + let profile: String? +} + +struct Loaded { + var sens = false + var settings = false + var preferences = false + var targets = false + var carbratios = false + var basalProfiles = false +} + +struct ProfileList: JSON { + var profiles: String +} + +struct MigratedMeals: Codable { + var carbs: Decimal + var dish: String + var fat: Decimal + var protein: Decimal +} + +struct MigratedOverridePresets: Codable { + var advancedSettings: Bool + var cr: Bool + var date: Date + var duration: Decimal + var emoji: String + var end: Decimal + var id: String + var indefininite: Bool + var isf: Bool + var isndAndCr: Bool + var maxIOB: Decimal + var name: String + var overrideMaxIOB: Bool + var percentage: Double + var smbAlwaysOff: Bool + var smbIsOff: Bool + var smbMinutes: Decimal + var start: Decimal + var target: Decimal + var uamMinutes: Decimal +} + +struct MealDatabase: JSON { + var report = "mealPresets" + var profile: String + var presets: [MigratedMeals] + let enteredBy: String +} + +struct OverrideDatabase: JSON { + var report = "overridePresets" + var profile: String + var presets: [MigratedOverridePresets] + let enteredBy: String +} diff --git a/FreeAPS/Sources/Models/NightscoutPreferences.swift b/FreeAPS/Sources/Models/NightscoutPreferences.swift deleted file mode 100644 index f02213772f..0000000000 --- a/FreeAPS/Sources/Models/NightscoutPreferences.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -struct NightscoutPreferences: JSON { - var report = "preferences" - let preferences: Preferences? - let enteredBy: String -} diff --git a/FreeAPS/Sources/Models/NightscoutSettings.swift b/FreeAPS/Sources/Models/NightscoutSettings.swift deleted file mode 100644 index 5a757372a7..0000000000 --- a/FreeAPS/Sources/Models/NightscoutSettings.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -struct NightscoutSettings: JSON { - var report = "settings" - let settings: FreeAPSSettings? - let enteredBy: String -} diff --git a/FreeAPS/Sources/Models/NightscoutStatistics.swift b/FreeAPS/Sources/Models/NightscoutStatistics.swift deleted file mode 100644 index e743c78ba6..0000000000 --- a/FreeAPS/Sources/Models/NightscoutStatistics.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -struct NightscoutStatistics: JSON { - let report = "statistics" - let dailystats: Statistics? - let justVersion: BareMinimum? -} diff --git a/FreeAPS/Sources/Models/NightscoutStatus.swift b/FreeAPS/Sources/Models/NightscoutStatus.swift index 4102dde9d2..4cae2e9088 100644 --- a/FreeAPS/Sources/Models/NightscoutStatus.swift +++ b/FreeAPS/Sources/Models/NightscoutStatus.swift @@ -52,4 +52,5 @@ struct NightscoutProfileStore: JSON { let units: String var enteredBy: String let store: [String: ScheduledNightscoutProfile] + let profile: String? } diff --git a/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift b/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift index f3b3138026..626b64fd62 100644 --- a/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift +++ b/FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift @@ -209,12 +209,9 @@ extension DataTable { let saveToJSON = BloodGlucose( _id: id, - direction: nil, + sgv: Int(glucose), date: Decimal(now.timeIntervalSince1970) * 1000, dateString: now, - unfiltered: nil, - filtered: nil, - noise: nil, glucose: Int(glucose), type: GlucoseType.manual.rawValue ) diff --git a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift index bc38ca3db4..353ac5b28d 100644 --- a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift +++ b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift @@ -780,7 +780,7 @@ extension Home { .font(.suggestionError) .padding(.bottom, 4) .padding(.top, 8) - Text(errorMessage).font(.buttonFont).foregroundColor(.loopRed) + Text(errorMessage).font(.suggestionError).fontWeight(.semibold).foregroundColor(.orange) } else if let suggestion = state.suggestion, (suggestion.bg ?? 100) == 400 { Text("Invalid CGM reading (HIGH).").font(.suggestionError).bold().foregroundColor(.loopRed).padding(.top, 8) Text("SMBs and High Temps Disabled.").font(.suggestionParts).foregroundColor(.white).padding(.bottom, 4) diff --git a/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift b/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift index 7527ddcefb..e3b4fe812f 100644 --- a/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift +++ b/FreeAPS/Sources/Modules/Sharing/View/SharingRootView.swift @@ -68,7 +68,7 @@ extension Sharing { Section { HStack { - Text(display ? state.identfier : "Tap to display") + Text(display ? state.identfier : NSLocalizedString("Tap to display", comment: "Token display button")) } .frame(maxWidth: .infinity, alignment: .center) .onTapGesture { display.toggle() } @@ -81,7 +81,7 @@ extension Sharing { } } } - header: { Text("\nYour recovery token") } + header: { Text("Your recovery token") } footer: { Text((copied && display) ? "" : display ? "Long press to copy" : "") diff --git a/FreeAPS/Sources/Services/Network/Database.swift b/FreeAPS/Sources/Services/Network/Database.swift new file mode 100644 index 0000000000..9af01b06b3 --- /dev/null +++ b/FreeAPS/Sources/Services/Network/Database.swift @@ -0,0 +1,422 @@ +import Combine +import Foundation + +class Database { + init(token: String) { + self.token = token + } + + private enum Config { + static let sharePath = "/upload.php" + static let versionPath = "/vcheck.php" + static let download = "/download.php?token=" + static let profileList = "§ion=profile_list" + static let retryCount = 2 + static let timeout: TimeInterval = 60 + } + + let url: URL = IAPSconfig.statURL + let token: String + + private let service = NetworkService() +} + +extension Database { + func fetchPreferences(_ name: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + "§ion=preferences&profile=" + name + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .decode(type: Preferences.self, decoder: JSONCoding.decoder) + .eraseToAnyPublisher() + } + + func moveProfiles(token: String, restoreToken: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + restoreToken + "&new_token=" + token + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func fetchProfiles() -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + Config.profileList + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .decode(type: ProfileList.self, decoder: JSONCoding.decoder) + .eraseToAnyPublisher() + } + + func fetchSettings(_ name: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + "§ion=settings&profile=" + name + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .customISO8601 + + return service.run(request) + .retry(Config.retryCount) + .decode(type: FreeAPSSettings.self, decoder: decoder) + .eraseToAnyPublisher() + } + + func fetchProfile(_ name: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + "§ion=profile&profile=" + name + + var request = URLRequest(url: components.url!) + request.allowsConstrainedNetworkAccess = false + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .decode(type: NightscoutProfileStore.self, decoder: JSONCoding.decoder) + .eraseToAnyPublisher() + } + + func deleteProfile(_ name: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + "§ion=profiles_delete&profile=" + name + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func fetchPumpSettings(_ name: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + "§ion=pumpSettings&profile=" + name + + var request = URLRequest(url: components.url!) + request.allowsConstrainedNetworkAccess = true + request.timeoutInterval = Config.timeout + + let decoder = JSONDecoder() + + return service.run(request) + .retry(Config.retryCount) + .decode(type: PumpSettings.self, decoder: decoder) + .eraseToAnyPublisher() + } + + func fetchTempTargets(_ name: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + "§ion=tempTargets&profile=" + name + + var request = URLRequest(url: components.url!) + request.allowsConstrainedNetworkAccess = true + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .decode(type: DatabaseTempTargets.self, decoder: JSONCoding.decoder) + .eraseToAnyPublisher() + } + + func fetchMealPressets(_ name: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + "§ion=mealPresets&profile=" + name + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .decode(type: MealDatabase.self, decoder: JSONCoding.decoder) + .eraseToAnyPublisher() + } + + func fetchOverridePressets(_ name: String) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.download + token + "§ion=overridePresets&profile=" + name + + var request = URLRequest(url: components.url!) + request.allowsConstrainedNetworkAccess = true + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .decode(type: OverrideDatabase.self, decoder: JSONCoding.decoder) + .eraseToAnyPublisher() + } + + func uploadSettingsToDatabase(_ profile: NightscoutProfileStore) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.sharePath + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + request.httpBody = try! JSONCoding.encoder.encode(profile) + request.httpMethod = "POST" + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func uploadStats(_ stats: NightscoutStatistics) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.sharePath + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try! JSONCoding.encoder.encode(stats) + request.httpMethod = "POST" + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func fetchVersion() -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.versionPath + + var request = URLRequest(url: components.url!) + request.allowsConstrainedNetworkAccess = true + request.timeoutInterval = Config.timeout + + return service.run(request) + .retry(Config.retryCount) + .decode(type: Version.self, decoder: JSONCoding.decoder) + .catch { error -> AnyPublisher in + warning(.nightscout, "Version fetching error: \(error.localizedDescription) \(request)") + return Just(Version(main: "", dev: "")).setFailureType(to: Swift.Error.self).eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + func uploadPrefs(_ prefs: NightscoutPreferences) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.sharePath + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + request.httpBody = try! JSONCoding.encoder.encode(prefs) + request.httpMethod = "POST" + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func uploadSettings(_ settings: NightscoutSettings) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.sharePath + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + request.httpBody = try! JSONCoding.encoder.encode(settings) + request.httpMethod = "POST" + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func uploadPumpSettings(_ settings: DatabasePumpSettings) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.sharePath + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + request.httpBody = try! JSONCoding.encoder.encode(settings) + request.httpMethod = "POST" + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func uploadTempTargets(_ targets: DatabaseTempTargets) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.sharePath + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + request.httpBody = try! JSONCoding.encoder.encode(targets) + request.httpMethod = "POST" + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func uploadMealPresets(_ presets: MealDatabase) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.sharePath + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + request.httpBody = try! JSONCoding.encoder.encode(presets) + request.httpMethod = "POST" + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + func uploaOverrridePresets(_ presets: OverrideDatabase) -> AnyPublisher { + var components = URLComponents() + components.scheme = url.scheme + components.host = url.host + components.port = url.port + components.path = Config.sharePath + + var request = URLRequest(url: components.url!) + request.timeoutInterval = Config.timeout + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + request.httpBody = try! JSONCoding.encoder.encode(presets) + request.httpMethod = "POST" + + return service.run(request) + .retry(Config.retryCount) + .map { _ in () } + .eraseToAnyPublisher() + } + + private func migrateMealPresets() -> [MigratedMeals] { + let meals = CoreDataStorage().fetchMealPresets() + return meals.map({ item -> MigratedMeals in + MigratedMeals( + carbs: (item.carbs ?? 0) as Decimal, + dish: item.dish ?? "", + fat: (item.fat ?? 0) as Decimal, + protein: (item.protein ?? 0) as Decimal + ) + }) + } + + private func migrateOverridePresets() -> [MigratedOverridePresets] { + let presets = OverrideStorage().fetchProfiles() + return presets.map({ item -> MigratedOverridePresets in + MigratedOverridePresets( + advancedSettings: item.advancedSettings, + cr: item.cr, + date: item.date ?? Date(), + duration: (item.duration ?? 0) as Decimal, + emoji: item.emoji ?? "", + end: (item.end ?? 0) as Decimal, + id: item.id ?? "", + indefininite: item.indefinite, + isf: item.isf, + isndAndCr: item.isfAndCr, + maxIOB: (item.maxIOB ?? 0) as Decimal, + name: item.name ?? "", + overrideMaxIOB: item.overrideMaxIOB, + percentage: item.percentage, + smbAlwaysOff: item.smbIsAlwaysOff, + smbIsOff: item.smbIsOff, + smbMinutes: (item.smbMinutes ?? 0) as Decimal, + start: (item.start ?? 0) as Decimal, + target: (item.target ?? 0) as Decimal, + uamMinutes: (item.uamMinutes ?? 0) as Decimal + ) + + }) + } + + func mealPresetDatabaseUpload(profile: String, token: String) -> MealDatabase { + MealDatabase(profile: profile, presets: migrateMealPresets(), enteredBy: token) + } + + func overridePresetDatabaseUpload(profile: String, token: String) -> OverrideDatabase { + OverrideDatabase(profile: profile, presets: migrateOverridePresets(), enteredBy: token) + } +} diff --git a/FreeAPS/Sources/Services/Network/NightscoutManager.swift b/FreeAPS/Sources/Services/Network/NightscoutManager.swift index 0ea68ef309..7d4bb63890 100644 --- a/FreeAPS/Sources/Services/Network/NightscoutManager.swift +++ b/FreeAPS/Sources/Services/Network/NightscoutManager.swift @@ -19,7 +19,7 @@ protocol NightscoutManager: GlucoseSource { func uploadManualGlucose() func uploadStatistics(dailystat: Statistics) func uploadVersion(json: BareMinimum) - func uploadPreferences(_ preferences: Preferences) + func uploadPreferences(_ preferences: NightscoutPreferences) func uploadProfileAndSettings(_: Bool) func uploadOverride(_ profile: String, _ duration: Double, _ date: Date) func deleteAnnouncements() @@ -62,12 +62,16 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { settingsManager.settings.uploadStats } + private var isVersionUploadEnabled: Bool { + settingsManager.settings.uploadVersion + } + private var isUploadGlucoseEnabled: Bool { settingsManager.settings.uploadGlucose } - private var isVersionUploadEnabled: Bool { - settingsManager.settings.uploadVersion + private var name: String { + CoreDataStorage().fetchSettingProfileName() } private var nightscoutAPI: NightscoutAPI? { @@ -95,6 +99,10 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { } } + private func saveToCoreData(_ name: String) { + CoreDataStorage().profileSettingUploaded(name: name) + } + func sourceInfo() -> [String: Any]? { if let ping = ping { return [GlucoseSourceKey.nightscoutPing.rawValue: ping] @@ -444,6 +452,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { debug(.nightscout, "Statistics uploaded") CoreDataStorage().saveStatUploadCount() UserDefaults.standard.set(false, forKey: IAPSconfig.newVersion) + self.uploadProfileAndSettings(true) case let .failure(error): debug(.nightscout, "Statistics upload failed" + error.localizedDescription) } @@ -477,20 +486,34 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { } } - func uploadPreferences(_ preferences: Preferences) { - let prefs = NightscoutPreferences( - preferences: settingsManager.preferences, enteredBy: getIdentifier() - ) - - let nightscout = NightscoutAPI(url: IAPSconfig.statURL) - + func uploadPreferences(_ preferences: NightscoutPreferences) { + let db = Database(token: preferences.enteredBy) processQueue.async { - nightscout.uploadPrefs(prefs) + db.uploadPrefs(preferences) .sink { completion in switch completion { case .finished: - debug(.nightscout, "Preferences uploaded to database") + debug(.nightscout, "Preferences uploaded to database. Profile: \(preferences.profile ?? "")") self.storage.save(preferences, as: OpenAPS.Nightscout.uploadedPreferences) + self.saveToCoreData(preferences.profile ?? "default") + case let .failure(error): + debug(.nightscout, "Preferences failed to upload to database " + error.localizedDescription) + } + } receiveValue: {} + .store(in: &self.lifetime) + } + } + + func uploadSettings(_ settings: NightscoutSettings) { + let db = Database(token: settings.enteredBy) + processQueue.async { + db.uploadSettings(settings) + .sink { completion in + switch completion { + case .finished: + debug(.nightscout, "Settings uploaded to database. Profile: \(settings.profile ?? "")") + self.storage.save(settings, as: OpenAPS.Nightscout.uploadedSettings) + self.saveToCoreData(settings.profile ?? "default") case let .failure(error): debug(.nightscout, error.localizedDescription) } @@ -499,22 +522,70 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { } } - func uploadSettings(_ settings: FreeAPSSettings) { - let sets = NightscoutSettings( - settings: settingsManager.settings, enteredBy: getIdentifier() - ) + private func uploadPumpSettingsToDatabase(_ settings: PumpSettings, token: String, name: String?) { + let upload = DatabasePumpSettings(settings: settings, enteredBy: token, profile: name) + processQueue.async { + Database(token: token).uploadPumpSettings(upload) + .sink { completion in + switch completion { + case .finished: + debug(.nightscout, "Pump settings uploaded to database. Profile: \(upload.profile ?? "")") + self.storage.save(settings, as: OpenAPS.Nightscout.uploadedPumpSettings) + self.saveToCoreData(name ?? "default") + case let .failure(error): + debug(.nightscout, "Pump settings failed to upload to database " + error.localizedDescription) + } + } receiveValue: {} + .store(in: &self.lifetime) + } + } - let nightscout = NightscoutAPI(url: IAPSconfig.statURL) + private func uploadTempTargetsToDatabase(_ targets: [TempTarget], token: String, name: String?) { + let upload = DatabaseTempTargets(tempTargets: targets, enteredBy: token, profile: name ?? "default") + processQueue.async { + Database(token: token).uploadTempTargets(upload) + .sink { completion in + switch completion { + case .finished: + debug(.nightscout, "Temp targets uploaded to database. Profile: \(upload.profile ?? "")") + self.storage.save(targets, as: OpenAPS.Nightscout.uploadedTempTargetsDatabase) + self.saveToCoreData(name ?? "default") + case let .failure(error): + debug(.nightscout, "Temp targets failed to upload to database " + error.localizedDescription) + } + } receiveValue: {} + .store(in: &self.lifetime) + } + } + private func uploadMealPresetsToDatabase(_ presets: MealDatabase, token: String) { processQueue.async { - nightscout.uploadSettings(sets) + Database(token: token).uploadMealPresets(presets) .sink { completion in switch completion { case .finished: - debug(.nightscout, "Settings uploaded to database") - self.storage.save(settings, as: OpenAPS.Nightscout.uploadedSettings) + debug(.nightscout, "Meal presets uploaded to database. Profile: \(presets.profile)") + self.storage.save(presets, as: OpenAPS.Nightscout.uploadedMealPresets) + self.saveToCoreData(presets.profile) case let .failure(error): - debug(.nightscout, error.localizedDescription) + debug(.nightscout, "Meal presets failed to upload to database " + error.localizedDescription) + } + } receiveValue: {} + .store(in: &self.lifetime) + } + } + + private func uploadOverridePresetsToDatabase(_ presets: OverrideDatabase, token: String) { + processQueue.async { + Database(token: token).uploaOverrridePresets(presets) + .sink { completion in + switch completion { + case .finished: + debug(.nightscout, "Override presets uploaded to database. Profile: \(presets.profile)") + self.storage.save(presets, as: OpenAPS.Nightscout.uploadedOverridePresets) + self.saveToCoreData(presets.profile) + case let .failure(error): + debug(.nightscout, "Override presets failed to upload to database " + error.localizedDescription) } } receiveValue: {} .store(in: &self.lifetime) @@ -626,60 +697,84 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { } func uploadProfileAndSettings(_ force: Bool) { - guard let sensitivities = storage.retrieve(OpenAPS.Settings.insulinSensitivities, as: InsulinSensitivities.self) else { + var loaded = Loaded() + + // Start trying retrieving files + let sensitivities = storage.retrieveFile(OpenAPS.Settings.insulinSensitivities, as: InsulinSensitivities.self) + if sensitivities != nil { + loaded.sens = true + debug(.nightscout, "NightscoutManager uploadProfile: file insulinSensitivities loaded") + } else { debug(.nightscout, "NightscoutManager uploadProfile: error loading insulinSensitivities") - return } - guard let settings = storage.retrieve(OpenAPS.FreeAPS.settings, as: FreeAPSSettings.self) else { + + let settings = storage.retrieveFile(OpenAPS.FreeAPS.settings, as: FreeAPSSettings.self) + if settings != nil { + loaded.settings = true + } else { debug(.nightscout, "NightscoutManager uploadProfile: error loading settings") - return } - guard let preferences = storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self) else { + + let preferences = storage.retrieveFile(OpenAPS.Settings.preferences, as: Preferences.self) + if preferences != nil { + loaded.preferences = true + } else { debug(.nightscout, "NightscoutManager uploadProfile: error loading preferences") - return } - guard let targets = storage.retrieve(OpenAPS.Settings.bgTargets, as: BGTargets.self) else { + + let targets = storage.retrieveFile(OpenAPS.Settings.bgTargets, as: BGTargets.self) + if targets != nil { + loaded.targets = true + } else { debug(.nightscout, "NightscoutManager uploadProfile: error loading bgTargets") - return } - guard let carbRatios = storage.retrieve(OpenAPS.Settings.carbRatios, as: CarbRatios.self) else { + + let carbRatios = storage.retrieveFile(OpenAPS.Settings.carbRatios, as: CarbRatios.self) + if carbRatios != nil { + loaded.carbratios = true + } else { debug(.nightscout, "NightscoutManager uploadProfile: error loading carbRatios") - return } - guard let basalProfile = storage.retrieve(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self) else { + + let basalProfile = storage.retrieveFile(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self) + if basalProfile != nil { + loaded.basalProfiles = true + } else { debug(.nightscout, "NightscoutManager uploadProfile: error loading basalProfile") - return } - let sens = sensitivities.sensitivities.map { item -> NightscoutTimevalue in + let token = getIdentifier() + + let sens = sensitivities?.sensitivities.map { item -> NightscoutTimevalue in NightscoutTimevalue( time: String(item.start.prefix(5)), value: item.sensitivity, timeAsSeconds: item.offset * 60 ) } - let target_low = targets.targets.map { item -> NightscoutTimevalue in + + let target_low = targets?.targets.map { item -> NightscoutTimevalue in NightscoutTimevalue( time: String(item.start.prefix(5)), value: item.low, timeAsSeconds: item.offset * 60 ) } - let target_high = targets.targets.map { item -> NightscoutTimevalue in + let target_high = targets?.targets.map { item -> NightscoutTimevalue in NightscoutTimevalue( time: String(item.start.prefix(5)), value: item.high, timeAsSeconds: item.offset * 60 ) } - let cr = carbRatios.schedule.map { item -> NightscoutTimevalue in + let cr = carbRatios?.schedule.map { item -> NightscoutTimevalue in NightscoutTimevalue( time: String(item.start.prefix(5)), value: item.ratio, timeAsSeconds: item.offset * 60 ) } - let basal = basalProfile.map { item -> NightscoutTimevalue in + let basal = basalProfile?.map { item -> NightscoutTimevalue in NightscoutTimevalue( time: String(item.start.prefix(5)), value: item.rate, @@ -696,8 +791,8 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { } var carbs_hr: Decimal = 0 - if let isf = sensitivities.sensitivities.map(\.sensitivity).first, - let cr = carbRatios.schedule.map(\.ratio).first, + if let isf = sensitivities?.sensitivities.map(\.sensitivity).first, + let cr = carbRatios?.schedule.map(\.ratio).first, isf > 0, cr > 0 { // CarbImpact -> Carbs/hr = CI [mg/dl/5min] * 12 / ISF [mg/dl/U] * CR [g/U] @@ -709,85 +804,180 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { carbs_hr = Decimal(round(Double(carbs_hr) * 10.0)) / 10 } - let ps = ScheduledNightscoutProfile( - dia: settingsManager.pumpSettings.insulinActionCurve, - carbs_hr: Int(carbs_hr), - delay: 0, - timezone: TimeZone.current.identifier, - target_low: target_low, - target_high: target_high, - sens: sens, - basal: basal, - carbratio: cr, - units: nsUnits - ) - let defaultProfile = "default" - - let now = Date() - let p = NightscoutProfileStore( - defaultProfile: defaultProfile, - startDate: now, - mills: Int(now.timeIntervalSince1970) * 1000, - units: nsUnits, - enteredBy: NigtscoutTreatment.local, - store: [defaultProfile: ps] - ) + if loaded.basalProfiles, loaded.carbratios, loaded.carbratios, loaded.sens, loaded.targets { + // Unknown errors, as it shouldn't happen here + guard let glucosetarget_low = target_low else { return } + guard let glucosetarget_high = target_high else { return } + guard let unwrappedSens = sens else { return } + guard let unwrappedBasal = basal else { return } + guard let unwrappedCR = cr else { return } + + let ps = ScheduledNightscoutProfile( + dia: settingsManager.pumpSettings.insulinActionCurve, + carbs_hr: Int(carbs_hr), + delay: 0, + timezone: TimeZone.current.identifier, + target_low: glucosetarget_low, + target_high: glucosetarget_high, + sens: unwrappedSens, + basal: unwrappedBasal, + carbratio: unwrappedCR, + units: nsUnits + ) + let defaultProfile = "default" + + let now = Date() + var p = NightscoutProfileStore( + defaultProfile: "default", + startDate: now, + mills: Int(now.timeIntervalSince1970) * 1000, + units: nsUnits, + enteredBy: NigtscoutTreatment.local, + store: [defaultProfile: ps], + profile: name + ) - let nightscout = NightscoutAPI(url: IAPSconfig.statURL) + let q = NightscoutProfileStore( + defaultProfile: "default", + startDate: now, + mills: Int(now.timeIntervalSince1970) * 1000, + units: nsUnits, + enteredBy: NigtscoutTreatment.local, + store: [defaultProfile: ps], + profile: name + ) + + // UPLOAD Profiles WHEN CHANGED + if let uploadedProfile = storage.retrieveFile(OpenAPS.Nightscout.uploadedProfile, as: NightscoutProfileStore.self), + (uploadedProfile.store["default"]?.rawJSON ?? "").sorted() == ps.rawJSON.sorted(), !force + { + NSLog("NightscoutManager uploadProfile, no profile change") + } else { + if let ns = nightscoutAPI, isUploadEnabled { + processQueue.async { + ns.uploadProfile(q) + .sink { completion in + switch completion { + case .finished: + self.storage.save(p, as: OpenAPS.Nightscout.uploadedProfile) + debug(.nightscout, "Profile uploaded") + case let .failure(error): + debug(.nightscout, error.localizedDescription) + } + } receiveValue: {} + .store(in: &self.lifetime) + } + } + } + + // UPLOAD Profiles to database WHEN CHANGED + if let uploadedProfile = storage.retrieveFile( + OpenAPS.Nightscout.uploadedProfileToDatabase, + as: DatabaseProfileStore.self + ), + (uploadedProfile.store["default"]?.rawJSON ?? "").sorted() == ps.rawJSON.sorted(), !force + { + NSLog("NightscoutManager uploadProfile to database, no profile change") + } else { + if isStatsUploadEnabled { + p.enteredBy = getIdentifier() + processQueue.async { + Database(token: token).uploadSettingsToDatabase(p) + .sink { completion in + switch completion { + case .finished: + debug(.nightscout, "Profiles uploaded to database. Profile: \(p.profile ?? "")") + self.storage.save(p, as: OpenAPS.Nightscout.uploadedProfileToDatabase) + case let .failure(error): + debug(.nightscout, error.localizedDescription) + } + } receiveValue: {} + .store(in: &self.lifetime) + } + } + } + } // UPLOAD PREFERNCES WHEN CHANGED - if let uploadedPreferences = storage.retrieve(OpenAPS.Nightscout.uploadedPreferences, as: Preferences.self), - uploadedPreferences.rawJSON.sorted() == preferences.rawJSON.sorted(), !force + if let uploadedPreferences = storage.retrieveFile(OpenAPS.Nightscout.uploadedPreferences, as: Preferences.self), + let unWrappedPreferences = preferences { - NSLog("NightscoutManager Preferences, preferences unchanged") - } else { uploadPreferences(preferences) } + if uploadedPreferences.rawJSON.sorted() != unWrappedPreferences.rawJSON.sorted() || + force + { + let prefs = NightscoutPreferences(preferences: unWrappedPreferences, enteredBy: token, profile: name) + uploadPreferences(prefs) + } else { + NSLog("NightscoutManager Preferences, preferences unchanged") + } + } else if loaded.preferences { + let prefs = NightscoutPreferences(preferences: preferences, enteredBy: token, profile: name) + uploadPreferences(prefs) + } // UPLOAD FreeAPS Settings WHEN CHANGED if let uploadedSettings = storage.retrieve(OpenAPS.Nightscout.uploadedSettings, as: FreeAPSSettings.self), - uploadedSettings.rawJSON.sorted() == settings.rawJSON.sorted(), !force + let unwrappedSettings = settings, uploadedSettings.rawJSON.sorted() == unwrappedSettings.rawJSON.sorted(), !force { NSLog("NightscoutManager Settings, settings unchanged") - } else { uploadSettings(settings) } + } else { + let sets = NightscoutSettings( + settings: settingsManager.settings, enteredBy: getIdentifier(), profile: name + ) + uploadSettings(sets) + } + + // UPLOAD PumpSettings WHEN CHANGED + if let pumpSettings = storage.retrieveFile(OpenAPS.Settings.settings, as: PumpSettings.self) { + if let uploadedSettings = storage.retrieve(OpenAPS.Nightscout.uploadedPumpSettings, as: PumpSettings.self), + uploadedSettings.rawJSON.sorted() == pumpSettings.rawJSON.sorted(), !force + { + NSLog("PumpSettings unchanged") + } else { uploadPumpSettingsToDatabase(pumpSettings, token: token, name: name) } - // UPLOAD Profiles WHEN CHANGED - if let uploadedProfile = storage.retrieve(OpenAPS.Nightscout.uploadedProfile, as: NightscoutProfileStore.self), - (uploadedProfile.store["default"]?.rawJSON ?? "").sorted() == ps.rawJSON.sorted(), !force - { - NSLog("NightscoutManager uploadProfile, no profile change") } else { - if let ns = nightscoutAPI, isUploadEnabled { - processQueue.async { - ns.uploadProfile(p) - .sink { completion in - switch completion { - case .finished: - self.storage.save(p, as: OpenAPS.Nightscout.uploadedProfile) - debug(.nightscout, "Profile uploaded") - case let .failure(error): - debug(.nightscout, error.localizedDescription) - } - } receiveValue: {} - .store(in: &self.lifetime) - } + debug(.nightscout, "UploadPumpSettings: error opening pump settings") + } + + // UPLOAD Temp Targets WHEN CHANGED + if let tempTargets = storage.retrieveFile(OpenAPS.FreeAPS.tempTargetsPresets, as: [TempTarget].self) { + if let uploadedTempTargets = storage.retrieve( + OpenAPS.Nightscout.uploadedTempTargetsDatabase, + as: [TempTarget].self + ), + uploadedTempTargets.rawJSON.sorted() == tempTargets.rawJSON.sorted(), !force + { + NSLog("Temp targets unchanged") + } else { uploadTempTargetsToDatabase(tempTargets, token: token, name: name) } + + } else { + debug(.nightscout, "UploadPumpSettings: error opening pump settings") + } + + // Upload Meal Presets when needed + let mealPresets = Database(token: token).mealPresetDatabaseUpload(profile: name, token: token) + if !mealPresets.presets.isEmpty { + if let uploadedMealPresets = storage.retrieveFile(OpenAPS.Nightscout.uploadedMealPresets, as: MealDatabase.self), + mealPresets.rawJSON.sorted() == uploadedMealPresets.rawJSON.sorted(), !force + { + NSLog("Meal Presets unchanged") + } else { + uploadMealPresetsToDatabase(mealPresets, token: token) } - if isStatsUploadEnabled { - var q = p - q.enteredBy = getIdentifier() - processQueue.async { - nightscout.uploadSettingsToDatabase(q) - .sink { completion in - switch completion { - case .finished: - debug(.nightscout, "Profiles uploaded to database") - if !self.isUploadEnabled { - self.storage.save(p, as: OpenAPS.Nightscout.uploadedProfile) - } - case let .failure(error): - debug(.nightscout, error.localizedDescription) - } - } receiveValue: {} - .store(in: &self.lifetime) - } + } + + // Upload Override Presets when needed + let overridePresets = Database(token: token).overridePresetDatabaseUpload(profile: name, token: token) + if !overridePresets.presets.isEmpty { + if let uploadedOverridePresets = storage.retrieveFile( + OpenAPS.Nightscout.uploadedOverridePresets, + as: OverrideDatabase.self + ), + overridePresets.rawJSON.sorted() == uploadedOverridePresets.rawJSON.sorted(), !force + { + NSLog("Override Presets unchanged") + } else { + uploadOverridePresetsToDatabase(overridePresets, token: token) } } } @@ -950,6 +1140,10 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { uploadTreatments(carbsStorage.nightscoutTretmentsNotUploaded(), fileToSave: OpenAPS.Nightscout.uploadedCarbs) } + private func loadFileFromStorage(name: String) -> RawJSON { + storage.retrieveRaw(name) ?? OpenAPS.defaults(for: name) + } + private func uploadTempTargets() { uploadTreatments(tempTargetsStorage.nightscoutTretmentsNotUploaded(), fileToSave: OpenAPS.Nightscout.uploadedTempTargets) } diff --git a/FreeAPS/Sources/Services/Storage/FileStorage.swift b/FreeAPS/Sources/Services/Storage/FileStorage.swift index 6332b514f1..bd29d2d532 100644 --- a/FreeAPS/Sources/Services/Storage/FileStorage.swift +++ b/FreeAPS/Sources/Services/Storage/FileStorage.swift @@ -11,11 +11,12 @@ protocol FileStorage { func remove(_ name: String) func rename(_ name: String, to newName: String) func transaction(_ exec: (FileStorage) -> Void) + func retrieveFile(_ name: String, as type: Value.Type) -> Value? func urlFor(file: String) -> URL? } -final class BaseFileStorage: FileStorage { +final class BaseFileStorage: FileStorage, Injectable { private let processQueue = DispatchQueue.markedQueue(label: "BaseFileStorage.processQueue", qos: .utility) func save(_ value: Value, as name: String) { @@ -43,6 +44,15 @@ final class BaseFileStorage: FileStorage { } } + func retrieveFile(_ name: String, as type: Value.Type) -> Value? { + if let loaded = retrieve(name, as: type) { + return loaded + } + let file = retrieveRaw(name) ?? OpenAPS.defaults(for: name) + save(file, as: name) + return retrieve(name, as: type) + } + func append(_ newValue: Value, to name: String) { processQueue.safeSync { try? Disk.append(newValue, to: name, in: .documents, decoder: JSONCoding.decoder, encoder: JSONCoding.encoder) From 82cd4c5a996e0aa68f6eed0b23ca65f83605f325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:48:54 +0200 Subject: [PATCH 05/12] Release 5.0.0 (#852) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Release 5.0.0 1. Hypo treatments (#801) Activate a hypo treatment override preset when selecting Hypo Treatment in the meals View. The activated override: 45 min, SMBs off, 90%, 6.5 mmol/l target. This will also skip the Bolus View. Make the necessary updates to update the override/s in Nightscout. Those who never records any hypo treatments in iAPS can disable this option in UI/UX settings. 2. Don't run TDD twice (https://github.com/Artificial-Pancreas/iAPS/commit/dd5cc61c5a3aaec421144eac207ab01ff557d4fe) An add some more printing to console to see time of each oref0 module. 3. Expand iAPS Live Activity (#808). Display Last loop, IOB, COB and eventual glucose prediction. Add support for both open loop mode and closed loop mode. https://github.com/Artificial-Pancreas/iAPS/pull/808 4. Move max carbs (#811). Some users don't know about this new setting. It has now been moved to a more intuitive location. 5. Resolve Calender issue introdced in commit af1acf9b2921aef5adb2c1181cbe154214960eb7 (#821) 6. Resolve issue https://github.com/Artificial-Pancreas/iAPS/issues/804 7. Refactor (https://github.com/Artificial-Pancreas/iAPS/pull/823) 8. Fix the rounding in small TIR chart. Every group together now always end up with 100 %. 9. Hide the COB chart when lacking carb entries. Hide the IOB chart when lacking insulin data. https://github.com/Artificial-Pancreas/iAPS/pull/824 10. Add Glucose drop to Header View Display rotating glucose drop. Current Glucose, loop minutes ago and glucose delta is now in highest contrast. The glucose delta is now an option in UI/UX settings (default is off). I recommend having “Always color glucose value” off in UI/UX settings, as this will make sure you’ll always get the glucose in highest contrast (black/white). https://discord.com/channels/1120154740857245808/1123065808130691124/1277393884053504072 11. Add pump shadows.. 12. Localize Header Clean up. 13. Fix default glucose delta setting * Display + in delta. Round manual glucose values separately. 14. Move infoPanel to be a more integral part of the header View Update animation duration. 15. Add label to the iAPS pill 16. Use original oref0 profile script (#805), by @bastiaanv https://discord.com/channels/1120154740857245808/1123065808130691124/1278662542511575041 17. Edit "Error" to "Status" (#761) 18 Bring back display of before and after the dynamic adjustment in iAPS pill. Bring back correct colour of Insulin 24h. Include CR. 19. New localizations and Crowdin translations. Thank you translators: Aleksandr Van-Zaam, Mirko Trierenberg, Typ1er, Mykola Yroslavadudko, Nicole van Elst - van den Hoek, Hung Nguyen, Salem Allebdi, aboeinas, Abdulrahman Alfantokh, 244877227, Alessandro Fogliani for new translations. We now have some Arabic translations. --- .gitignore | 5 +- Config.xcconfig | 2 +- .../Core_Data.xcdatamodel/contents | 5 +- .../zh-Hans.lproj/Localizable.strings | 4 +- .../CGMBLEKitUI/ar.lproj/Localizable.strings | 4 +- .../ar.lproj/TransmitterManagerSetup.strings | 2 +- .../vi.lproj/TransmitterManagerSetup.strings | 2 +- .../zh-Hans.lproj/Localizable.strings | 2 +- .../ar.lproj/Localizable.strings | 16 +- .../G7SensorKit/ar.lproj/Localizable.strings | 12 +- .../Resources/ar.lproj/Localizable.strings | 46 +- .../Resources/vi.lproj/Localizable.strings | 2 +- .../Resources/ar.lproj/Localizable.strings | 12 +- .../ar.lproj/Localizable.strings | 32 +- .../Resources/ar.lproj/Localizable.strings | 114 ++--- .../Resources/ar.lproj/Localizable.strings | 42 +- .../Resources/vi.lproj/Localizable.strings | 2 +- .../ar.lproj/Localizable.strings | 10 +- .../vi.lproj/Localizable.strings | 4 +- FreeAPS.xcodeproj/project.pbxproj | 4 + .../xcshareddata/swiftpm/Package.resolved | 2 +- .../glucoseDrops.imageset/Contents.json | 26 + .../glucoesDropBlack5_2.png | Bin 0 -> 36502 bytes .../glucoesDropClearNoPointerExtraWhite.png | Bin 0 -> 34090 bytes .../Resources/javascript/bundle/profile.js | 3 +- .../javascript/prepare/determine-basal.js | 5 +- .../javascript/prepare/middleware.js | 9 +- .../Resources/javascript/prepare/profile.js | 46 +- .../defaults/freeaps/freeaps_settings.json | 5 +- FreeAPS/Sources/APS/APSManager.swift | 5 +- FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift | 82 +++- .../Sources/APS/Storage/CoreDataStorage.swift | 12 + .../Sources/APS/Storage/OverrideStorage.swift | 1 + .../Main/ar.lproj/Localizable.strings | 463 +++++++++--------- .../Main/ca.lproj/Localizable.strings | 2 +- .../Main/da.lproj/Localizable.strings | 19 +- .../Main/de.lproj/Localizable.strings | 20 +- .../Main/en.lproj/Localizable.strings | 16 +- .../Main/es.lproj/Localizable.strings | 16 +- .../Main/fi.lproj/Localizable.strings | 16 +- .../Main/fr.lproj/Localizable.strings | 18 +- .../Main/he.lproj/Localizable.strings | 16 +- .../Main/hu.lproj/Localizable.strings | 16 +- .../Main/it.lproj/Localizable.strings | 34 +- .../Main/nb.lproj/Localizable.strings | 16 +- .../Main/nl.lproj/Localizable.strings | 34 +- .../Main/pl.lproj/Localizable.strings | 16 +- .../Main/pt-BR.lproj/Localizable.strings | 16 +- .../Main/pt-PT.lproj/Localizable.strings | 16 +- .../Main/ru.lproj/Localizable.strings | 18 +- .../Main/sk.lproj/Localizable.strings | 16 +- .../Main/sv.lproj/Localizable.strings | 20 +- .../Main/tr.lproj/Localizable.strings | 16 +- .../Main/uk.lproj/Localizable.strings | 18 +- .../Main/vi.lproj/Localizable.strings | 46 +- .../Main/zh-Hans.lproj/Localizable.strings | 16 +- FreeAPS/Sources/Models/Configs.swift | 2 + FreeAPS/Sources/Models/FreeAPSSettings.swift | 10 + .../Modules/AddCarbs/AddCarbsStateModel.swift | 55 ++- .../AddCarbs/View/AddCarbsRootView.swift | 13 +- .../Sources/Modules/Home/HomeStateModel.swift | 11 + .../Home/View/Header/CurrentGlucoseView.swift | 174 ++++--- .../Modules/Home/View/Header/LoopView.swift | 29 +- .../Modules/Home/View/Header/PumpView.swift | 2 + .../Modules/Home/View/HomeRootView.swift | 379 +++++++------- .../Home/View/Previews/ActiveCOBView.swift | 45 +- .../Home/View/Previews/ActiveIOBView.swift | 82 +++- .../Home/View/Previews/LoopsView.swift | 32 +- .../Modules/Home/View/Previews/TIRView.swift | 309 +++++------- .../OverrideProfilesStateModel.swift | 4 +- .../PreferencesEditorStateModel.swift | 2 + .../View/PreferencesEditorRootView.swift | 27 + .../PumpSettingsEditorStateModel.swift | 3 - .../View/PumpSettingsEditorRootView.swift | 4 - .../Settings/View/SettingsRootView.swift | 17 +- .../StatConfig/StatConfigStateModel.swift | 4 + .../StatConfig/View/StatConfigRootView.swift | 2 + .../Services/Calendar/CalendarManager.swift | 18 +- .../LiveActivity/LiveActitiyShared.swift | 5 + .../LiveActivity/LiveActivityBridge.swift | 133 +++-- .../Overrides/OverrideShortcuts.swift | 4 + FreeAPS/Sources/Views/TagCloudView.swift | 2 +- FreeAPS/Sources/Views/ViewModifiers.swift | 56 ++- LiveActivity/LiveActivity.swift | 196 ++++++-- fastlane/Fastfile | 2 +- package.json | 8 + scripts/webpack.config.js | 38 +- 87 files changed, 1972 insertions(+), 1098 deletions(-) create mode 100644 FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/Contents.json create mode 100644 FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/glucoesDropBlack5_2.png create mode 100644 FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/glucoesDropClearNoPointerExtraWhite.png create mode 100644 package.json diff --git a/.gitignore b/.gitignore index 4a72f1f123..aea4123494 100644 --- a/.gitignore +++ b/.gitignore @@ -81,4 +81,7 @@ fastlane/FastlaneRunner ConfigOverride.xcconfig -branch.txt \ No newline at end of file +branch.txt +package-lock.json +node_modules +oref0 diff --git a/Config.xcconfig b/Config.xcconfig index 3ad19d0021..4bc8c1e9dc 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -1,5 +1,5 @@ APP_DISPLAY_NAME = iAPS -APP_VERSION = 4.8.0 +APP_VERSION = 5.0.0 APP_BUILD_NUMBER = 1 COPYRIGHT_NOTICE = DEVELOPER_TEAM = ##TEAM_ID## diff --git a/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents b/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents index 0f34a2efc9..8a0bf22421 100644 --- a/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents +++ b/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -160,6 +160,7 @@ + @@ -198,4 +199,4 @@ - \ No newline at end of file + diff --git a/Dependencies/CGMBLEKit/CGMBLEKit/zh-Hans.lproj/Localizable.strings b/Dependencies/CGMBLEKit/CGMBLEKit/zh-Hans.lproj/Localizable.strings index 5a6ae3671a..cfdf4e29b7 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKit/zh-Hans.lproj/Localizable.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKit/zh-Hans.lproj/Localizable.strings @@ -2,7 +2,7 @@ "Dexcom G5" = "Dexcom G 5"; /* CGM display title */ -"Dexcom G6" = "Dexcom G6"; +"Dexcom G6" = "德康 G6"; /* Error description for unreliable state */ "Glucose data is unavailable" = "葡萄糖数据不可用"; @@ -14,7 +14,7 @@ "OK" = "Ok"; /* invlid config error description */ -"Peripheral command was invalid" = "Peripheral command was invalid"; +"Peripheral command was invalid" = "外围命令无效"; /* Timeout error description */ "Peripheral did not respond in time" = "外设没有及时响应"; diff --git a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings index 789c5839f8..2fbdf8577b 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/Localizable.strings @@ -8,7 +8,7 @@ "Cancel" = "إلغاء"; /* Title describing glucose date */ -"Date" = "Date"; +"Date" = "التاريخ"; /* Button title to delete CGM Title text for the button to remove a CGM from Loop */ @@ -51,7 +51,7 @@ Title text for the button to remove a CGM from Loop */ "Transmitter Age" = "عمر جهاز الإرسال"; /* The title text for the Dexcom G5/G6 transmitter ID config value */ -"Transmitter ID" = "Transmitter ID"; +"Transmitter ID" = "معرف المرسل"; /* Title describing glucose trend */ "Trend" = "إتجاه"; diff --git a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings index 1ab79b565f..969c54582c 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKitUI/ar.lproj/TransmitterManagerSetup.strings @@ -11,7 +11,7 @@ "Qub-6B-0aB.footerTitle" = "يمكن العثور على معرف جهاز الإرسال مطبوعا على الجزء الخلفي من الجهاز، على جانب الصندوق الذي أتى به، ومن داخل قوائم الإعدادات الخاصة بالمستلم والتطبيق الموبايل."; /* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */ -"Qub-6B-0aB.headerTitle" = "Transmitter ID"; +"Qub-6B-0aB.headerTitle" = "معرف المرسل"; /* 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" = "يمكن تحميل البيانات عبر الإنترنت من تطبيق Share عند فشل اتصال المرسل."; diff --git a/Dependencies/CGMBLEKit/CGMBLEKitUI/vi.lproj/TransmitterManagerSetup.strings b/Dependencies/CGMBLEKit/CGMBLEKitUI/vi.lproj/TransmitterManagerSetup.strings index 028f87311c..65caa60ed2 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKitUI/vi.lproj/TransmitterManagerSetup.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKitUI/vi.lproj/TransmitterManagerSetup.strings @@ -5,7 +5,7 @@ "Dds-49-o7G.title" = "Cài đặt Transmitter"; /* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */ -"GOT-KQ-cEh.text" = "Chi tiết"; +"GOT-KQ-cEh.text" = "Chi tiết"; /* 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" = "Số ID của Transmitter có thể được tìm thấy trên vỏ hộp hoặc bên hông hộp và trong phần Menu cài đặt cũng như trên ứng dụng của điện thoại."; diff --git a/Dependencies/CGMBLEKit/CGMBLEKitUI/zh-Hans.lproj/Localizable.strings b/Dependencies/CGMBLEKit/CGMBLEKitUI/zh-Hans.lproj/Localizable.strings index 7606ebf3c8..a73353326b 100644 --- a/Dependencies/CGMBLEKit/CGMBLEKitUI/zh-Hans.lproj/Localizable.strings +++ b/Dependencies/CGMBLEKit/CGMBLEKitUI/zh-Hans.lproj/Localizable.strings @@ -39,7 +39,7 @@ Title text for the button to remove a CGM from Loop */ "Remote Data Synchronization" = "远程数据同步"; /* Title describing sensor expiration */ -"Sensor Expires" = "Sensor Expires"; +"Sensor Expires" = "传感器已过期"; /* Title describing past sensor expiration */ "Sensor Expired" = "传感器已过期"; diff --git a/Dependencies/G7SensorKit/G7SensorKitUI/ar.lproj/Localizable.strings b/Dependencies/G7SensorKit/G7SensorKitUI/ar.lproj/Localizable.strings index 90c967438f..56920cc985 100644 --- a/Dependencies/G7SensorKit/G7SensorKitUI/ar.lproj/Localizable.strings +++ b/Dependencies/G7SensorKit/G7SensorKitUI/ar.lproj/Localizable.strings @@ -11,10 +11,10 @@ "Bluetooth" = "البلوتوث"; /* Button text to cancel G7 setup */ -"Cancel" = "Cancel"; +"Cancel" = "إلغاء"; /* No comment provided by engineer. */ -"Configuration" = "Configuration"; +"Configuration" = "ضبط"; /* title for g7 settings connection status when connected */ "Connected" = "Connected"; @@ -26,14 +26,14 @@ "Continue" = "Continue"; /* Button label for removing CGM */ -"Delete CGM" = "Delete CGM"; +"Delete CGM" = "حذف المستشعر"; /* Navigation bar title for G7SettingsView Title on WelcomeView */ "Dexcom G7" = "ديكسكوم G7"; /* No comment provided by engineer. */ -"Done" = "Done"; +"Done" = "تمّ"; /* Field label */ "Glucose" = "Glucose"; @@ -60,7 +60,7 @@ "LOW" = "منخفض"; /* title for g7 settings row showing BLE Name */ -"Name" = "Name"; +"Name" = "الاسم"; /* No comment provided by engineer. */ "Scan for new sensor" = "البحث عن مستشعر جديد"; @@ -84,7 +84,7 @@ "Sensor\nIssue" = "مشكلة في المستشعر\n"; /* G7 Status highlight text for sensor warmup */ -"Sensor\nWarmup" = "Sensor\nWarmup"; +"Sensor\nWarmup" = "إحماء\nالمستشعر"; /* title for g7 settings row showing sensor expiration time */ "Sensor Expiration" = "إنتهت صلاحية المستشعر"; @@ -99,13 +99,13 @@ "Sensor failed" = "فشل الحساس"; /* title for g7 settings row showing sensor start time */ -"Sensor Start" = "Start sensor"; +"Sensor Start" = "بدء الحساس"; /* G7 Status highlight text for signal loss */ "Signal\nLoss" = "فقدان الإشارة\n"; /* Field label */ -"Time" = "Time"; +"Time" = "الوقت"; /* Field label */ "Trend" = "إتجاه"; diff --git a/Dependencies/G7SensorKit/ar.lproj/Localizable.strings b/Dependencies/G7SensorKit/ar.lproj/Localizable.strings index a1087c7584..2fa0e844ac 100644 --- a/Dependencies/G7SensorKit/ar.lproj/Localizable.strings +++ b/Dependencies/G7SensorKit/ar.lproj/Localizable.strings @@ -8,7 +8,7 @@ "Continue" = "Continue"; /* Button text to cancel G7 setup */ -"Cancel" = "Cancel"; +"Cancel" = "إلغاء"; /* Error description for unreliable state */ "Glucose data is unavailable" = "قراءات السكر غير متوفرة"; @@ -45,14 +45,14 @@ "Last Reading" = "آخر قراءة"; -"Time" = "Time"; +"Time" = "الوقت"; "Trend" = "إتجاه"; "Bluetooth" = "البلوتوث"; /* title for g7 settings row showing BLE Name */ -"Name" = "Name"; +"Name" = "الاسم"; /* title for g7 settings connection status when scanning */ "Scanning" = "يتم المسح"; @@ -67,7 +67,7 @@ "Last Connect" = "آخر اتصال"; /* Configuration */ -"Configuration" = "Configuration"; +"Configuration" = "ضبط"; /* title for g7 config settings to upload readings */ "Upload Readings" = "رفع القراءات"; @@ -76,7 +76,7 @@ "Scan for new sensor" = "البحث عن مستشعر جديد"; /* Button label for removing CGM */ -"Delete CGM" = "Delete CGM"; +"Delete CGM" = "حذف المستشعر"; /* No glucose value representation (3 dashes for mg/dL) */ "– – –" = "– – –"; @@ -126,4 +126,4 @@ "Sensor\nIssue" = "مشكلة في المستشعر\n"; /* G7 Status highlight text for sensor warmup */ -"Sensor\nWarmup" = "Sensor\nWarmup"; +"Sensor\nWarmup" = "إحماء\nالمستشعر"; diff --git a/Dependencies/MinimedKit/MinimedKit/Resources/ar.lproj/Localizable.strings b/Dependencies/MinimedKit/MinimedKit/Resources/ar.lproj/Localizable.strings index 8a1f932e45..905d18d193 100644 --- a/Dependencies/MinimedKit/MinimedKit/Resources/ar.lproj/Localizable.strings +++ b/Dependencies/MinimedKit/MinimedKit/Resources/ar.lproj/Localizable.strings @@ -1,74 +1,74 @@ /* Communications error for a bolus currently running */ -"A bolus is already in progress" = "A bolus is already in progress"; +"A bolus is already in progress" = "هناك جرعة جارية بالفعل"; /* The description of AlarmClockReminderPumpEvent */ -"AlarmClockReminder" = "AlarmClockReminder"; +"AlarmClockReminder" = "تذكير المنبه"; /* The description of AlarmSensorPumpEvent */ -"AlarmSensor" = "AlarmSensor"; +"AlarmSensor" = "تنبيه الحساس"; /* Describing the battery chemistry as Alkaline */ "Alkaline" = "Alkaline"; /* The format string description of a BasalProfileStartPumpEvent. (1: The index of the profile)(2: The basal rate) */ -"Basal Profile %1$@: %2$@ U/hour" = "Basal Profile %1$@: %2$@ U/hour"; +"Basal Profile %1$@: %2$@ U/hour" = "ملف تعريف القاعدي %1$@: %2$@ U/hour"; /* Pump error code when bolus is in progress */ -"Bolus in progress" = "Bolus in progress"; +"Bolus in progress" = "جرعة قيد التنفيذ"; /* Suggestions for diagnosing a command refused pump error */ "Check that the pump is not suspended or priming, or has a percent temp basal type" = "Check that the pump is not suspended or priming, or has a percent temp basal type"; /* Pump error code returned when command refused */ -"Command refused" = "Command refused"; +"Command refused" = "تم رفض الأمر"; /* No comment provided by engineer. */ -"Comms with another pump detected" = "Comms with another pump detected."; +"Comms with another pump detected" = "تم اكتشاف الاتصال مع مضخة أخرى."; /* Error description */ -"Decoding Error" = "Decoding Error"; +"Decoding Error" = "خطأ في فك التشفير"; /* Error description */ -"Device Error" = "Device Error"; +"Device Error" = "خطأ في الجهاز"; /* Describing the pump history insulin data source */ -"Event History" = "Event History"; +"Event History" = "سجل الأحداث"; /* Format string for failure reason. (1: The operation being performed) (2: The response data) */ -"Invalid response during %1$@: %2$@" = "Invalid response during %1$@: %2$@"; +"Invalid response during %1$@: %2$@" = "استجابة غير صحيحة خلال %1$@: %2$@"; /* Describing the battery chemistry as Lithium */ -"Lithium" = "Lithium"; +"Lithium" = "ليثيوم"; /* Recovery suggestion */ -"Make sure your RileyLink is nearby and powered on" = "Make sure your RileyLink is nearby and powered on"; +"Make sure your RileyLink is nearby and powered on" = "تأكد من أن RileyLink الخاص بك قريب ويعمل"; /* Pump error code describing max setting exceeded */ -"Max setting exceeded" = "Max setting exceeded"; +"Max setting exceeded" = "تم تجاوز الحد الأقصى للإعدادات"; /* Pump title (1: model number) */ -"Minimed %@" = "Minimed %@"; +"Minimed %@" = "تم تصغير %@"; /* Generic title of the minimed pump manager */ -"Minimed 500/700 Series" = "Minimed 500/700 Series"; +"Minimed 500/700 Series" = "الحد الأدنى 500/700 سلسلة"; /* Describing the North America pump region */ -"North America" = "North America"; +"North America" = "أمريكا الشماليّة"; /* No comment provided by engineer. */ -"Pump did not respond" = "Pump did not respond"; +"Pump did not respond" = "المضخة لم تستجب"; /* Error description */ -"Pump Error" = "Pump Error"; +"Pump Error" = "خطأ في المضخة"; /* No comment provided by engineer. */ -"Pump is suspended" = "Pump is suspended"; +"Pump is suspended" = "تم تعليق المضخة"; /* No comment provided by engineer. */ -"Pump responded unexpectedly" = "Pump responded unexpectedly"; +"Pump responded unexpectedly" = "مضخة استجابت بشكل غير متوقع"; /* 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$@)" = "PumpMessage(%1$@, %2$@, %3$@, %4$@)"; +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "رسالة المضخة(%1$@، %2$@، %3$@، %4$@)"; /* Describing the reservoir insulin data source */ "Reservoir" = "الخزان"; @@ -89,7 +89,7 @@ "Unknown pump error code: %1$@" = "Unknown pump error code: %1$@"; /* No comment provided by engineer. */ -"Unknown pump model: %@" = "Unknown pump model: %@"; +"Unknown pump model: %@" = "نَموذج مضخة غير معروف: %@"; /* Format string for an unknown response. (1: The operation being performed) (2: The response data) */ "Unknown response during %1$@: %2$@" = "Unknown response during %1$@: %2$@"; diff --git a/Dependencies/MinimedKit/MinimedKit/Resources/vi.lproj/Localizable.strings b/Dependencies/MinimedKit/MinimedKit/Resources/vi.lproj/Localizable.strings index a5eb87a6b1..ea4dad177a 100644 --- a/Dependencies/MinimedKit/MinimedKit/Resources/vi.lproj/Localizable.strings +++ b/Dependencies/MinimedKit/MinimedKit/Resources/vi.lproj/Localizable.strings @@ -62,7 +62,7 @@ "Pump Error" = "Lỗi bơm"; /* No comment provided by engineer. */ -"Pump is suspended" = "Bơm đang tạm ngưng"; +"Pump is suspended" = "Bơm đã tạm ngưng"; /* No comment provided by engineer. */ "Pump responded unexpectedly" = "Bơm phản ứng bất ngờ"; diff --git a/Dependencies/MinimedKit/MinimedKitUI/Resources/ar.lproj/Localizable.strings b/Dependencies/MinimedKit/MinimedKitUI/Resources/ar.lproj/Localizable.strings index 015607ef1e..d08b6d1aaf 100644 --- a/Dependencies/MinimedKit/MinimedKitUI/Resources/ar.lproj/Localizable.strings +++ b/Dependencies/MinimedKit/MinimedKitUI/Resources/ar.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; +"%@ dB" = "%@ ديسيبل"; /* Format string for reservoir volume. (1: The localized volume) */ "%@U" = "%@U"; @@ -47,7 +47,7 @@ "Changing" = "Changing"; /* Progress message for changing pump time. */ -"Changing time…" = "Changing time…"; +"Changing time…" = "تغيير الوقت…"; /* Instructions on selecting battery chemistry type */ "Choose the type of battery you are using in your pump for better alerting about low battery conditions." = "Choose the type of battery you are using in your pump for better alerting about low battery conditions."; @@ -56,7 +56,7 @@ "Configuration" = "المعطيات"; /* Button title to connect to pump during setup */ -"Connect" = "Connect"; +"Connect" = "توصيل"; /* Text for continue button */ "Continue" = "Continue"; @@ -66,7 +66,7 @@ "Delete Pump" = "Delete Pump"; /* Header for devices section of RileyLinkSetupView */ -"Devices" = "Devices"; +"Devices" = "الأجهزة"; /* Description for option to not use MySentry */ "Do not use MySentry" = "Do not use MySentry"; @@ -114,7 +114,7 @@ "Medtronic pump models 523, 723, 554, and 754 have a feature called 'MySentry' that periodically broadcasts the reservoir and pump battery levels. Listening for these broadcasts allows Loop to communicate with the pump less frequently, which can increase pump battery life. However, when using this feature the RileyLink stays awake more of the time and uses more of its own battery. Enabling this may lengthen pump battery life, while disabling it may lengthen RileyLink battery life. This setting is ignored for other pump models." = "Medtronic pump models 523, 723, 554, and 754 have a feature called 'MySentry' that periodically broadcasts the reservoir and pump battery levels. Listening for these broadcasts allows Loop to communicate with the pump less frequently, which can increase pump battery life. However, when using this feature the RileyLink stays awake more of the time and uses more of its own battery. Enabling this may lengthen pump battery life, while disabling it may lengthen RileyLink battery life. This setting is ignored for other pump models."; /* Value string for MySentry config when MySentry is not being used */ -"No" = "No"; +"No" = "لا"; /* Message display when no response from tuning pump */ "No response" = "No response"; @@ -214,7 +214,7 @@ "Use MySentry" = "Use MySentry"; /* Value string for MySentry config when MySentry is being used */ -"Yes" = "Yes"; +"Yes" = "نعم"; /* Button text to confirm pump time sync */ "Yes, Sync to Current Time" = "Yes, Sync to Current Time"; diff --git a/Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings b/Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings index d578a852ca..7b44c14911 100644 --- a/Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings +++ b/Dependencies/OmniBLE/Localizations/ar.lproj/Localizable.strings @@ -75,7 +75,7 @@ "Pod Activated" = "Pod Activated"; /* */ -"Notification Settings" = "Notification Settings"; +"Notification Settings" = "إعدادات الإشعارات"; /* */ "Confidence Reminders" = "Confidence Reminders"; @@ -147,7 +147,7 @@ "minute" = "minute"; /* Unit for plural minutes in pod life remaining */ -"minutes" = "minutes"; +"minutes" = "دقائق"; /* Title of insulin delivery section */ "Insulin Delivery" = "Insulin Delivery"; @@ -165,7 +165,7 @@ "Device Details" = "Device Details"; /* Section header for configuration section */ -"Configuration" = "Configuration"; +"Configuration" = "ضبط"; /* Settings page link description when next lifecycle action is to finish deactivation */ "Finish deactivation" = "Finish deactivation"; @@ -312,7 +312,7 @@ "Resume" = "Resume"; /* */ -"Bolus" = "Bolus"; +"Bolus" = "الجرعة"; /* */ "Cancel Bolus" = "Cancel Bolus"; @@ -345,7 +345,7 @@ "Activity" = "Activity"; /* Section header for configuration section */ -"Configuration" = "Configuration"; +"Configuration" = "ضبط"; /* Title for previous pod page */ "Previous Pod" = "Previous Pod"; @@ -472,7 +472,7 @@ "Paired" = "Paired"; /* Cancel button text in navigation bar on pair pod UI */ -"Cancel" = "Cancel"; +"Cancel" = "إلغاء"; /* Alert title for cancel pairing modal */ "Are you sure you want to cancel Pod setup?" = "Are you sure you want to cancel Pod setup?"; @@ -529,10 +529,10 @@ "The window on the top of the Pod should be colored pink when the cannula is properly inserted into the skin." = "The window on the top of the Pod should be colored pink when the cannula is properly inserted into the skin."; /* Button label for user to answer cannula was properly inserted */ -"Yes" = "Yes"; +"Yes" = "نعم"; /* Button label for user to answer cannula was not properly inserted */ -"No" = "No"; +"No" = "لا"; /* Pod pairing action button text while pairing */ "Pairing..." = "Pairing..."; @@ -553,7 +553,7 @@ "Scheduled Reminder" = "Scheduled Reminder"; /* Label for expiration reminder row */ -"Time" = "Time"; +"Time" = "الوقت"; /* Action button title to continue at Setup Complete */ "Finish Setup" = "Finish Setup"; @@ -603,10 +603,10 @@ /* Description text for critical alerts */ "The reminders above will not sound if your device is in Silent or Do Not Disturb mode.\n\nThere are other critical Pod alerts and alarms that will sound even if your device is set to Silent or Do Not Disturb mode." = "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"; +"Notification Settings" = "إعدادات الإشعارات"; /* Label for scheduled reminder value row */ -"Time" = "Time"; +"Time" = "الوقت"; /* Value text for no expiration reminder */ "No Reminder" = "No Reminder"; @@ -638,7 +638,7 @@ "Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate."; /* Label text for temporary basal rate summary */ -"Rate" = "Rate"; +"Rate" = "معدّل"; /* Insulin unit per hour */ "U/hr" = "U/hr"; @@ -718,7 +718,7 @@ "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."; /* Cancel button title */ -"Cancel" = "Cancel"; +"Cancel" = "إلغاء"; /* Text for continue button on PodSetupView */ "Continue" = "Continue"; @@ -742,7 +742,7 @@ "Low Reservoir" = "Low Reservoir"; /* */ -"Save" = "Save"; +"Save" = "إحفظ"; /* hr (short for hour) */ "hr" = "hr"; @@ -814,7 +814,7 @@ "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"; +"%@ ago" = "منذ ‭%@"; /* Title string for SilencePodPreference.enabled */ "Silenced" = "Silenced"; @@ -852,7 +852,7 @@ "Read Pod Status" = "Read Pod Status"; /* Title of done button on OmnipodSettingsView */ -"Done" = "Done"; +"Done" = "تمّ"; /* Title for the pod diagnostic view */ "Pod Diagnostics" = "Pod Diagnostics"; diff --git a/Dependencies/OmniKit/OmniKit/Resources/ar.lproj/Localizable.strings b/Dependencies/OmniKit/OmniKit/Resources/ar.lproj/Localizable.strings index 496f0367fb..cf86c58c03 100644 --- a/Dependencies/OmniKit/OmniKit/Resources/ar.lproj/Localizable.strings +++ b/Dependencies/OmniKit/OmniKit/Resources/ar.lproj/Localizable.strings @@ -1,51 +1,51 @@ /* Description for an inactive alert modifier */ -" (inactive)" = " (inactive)"; +" (inactive)" = "(غير نشط)"; /* Format string for low battery alert body for RileyLink. (1: device name) */ -"\"%1$@\" has a low battery" = "\"%1$@\" has a low battery"; +"\"%1$@\" has a low battery" = "%1$@\" بطارية منخفضة"; /* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; +"%@ dB" = "%@ ديسيبل"; /* Format string for alert content body for lowReservoir pod alert. (1: reminder value) */ "%1$@ insulin or less remaining in Pod. Change Pod soon." = "%1$@ insulin or less remaining in Pod. Change Pod soon."; /* Format string for activation time exceeded Pod state when activation not completed in the time allowed */ -"Activation time exceeded" = "Activation time exceeded"; +"Activation time exceeded" = "تجاوز الوقت المحدد للتفعيل"; /* Description for auto-off */ -"Auto-off" = "Auto-Off"; +"Auto-off" = "إيقاف تلقائي"; /* Description for auto-off alarm */ -"Auto-off alarm" = "Auto-off alarm"; +"Auto-off alarm" = "إيقاف تلقائي للمنبه"; /* Pod state when basal initialized */ -"Basal initialized" = "Basal initialized"; +"Basal initialized" = "تهيئة الانسولين القاعدي"; /* Pod state when running below fifty units */ -"Below 50 units" = "Below 50 units"; +"Below 50 units" = "أقل من 50 وحدة"; /* Pump Event title for UnfinalizedDose with doseType of .bolus */ -"Bolus" = "Bolus"; +"Bolus" = "الجرعة"; /* Error message shown when operation could not be completed due to existing bolus in progress */ -"Bolus in progress" = "Bolus in progress"; +"Bolus in progress" = "جرعة قيد التنفيذ"; /* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ -"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; +"Bolus: %1$@U %2$@ %3$@ %4$@" = "جرعة: %1$@U %2$@ %3$@ %4$@"; /* Delivery status when bolusing */ -"Bolusing" = "Bolusing"; +"Bolusing" = "يضخ"; /* Delivery status when bolusing and temp basal is running */ -"Bolusing with temp basal" = "Bolusing with temp basal"; +"Bolusing with temp basal" = "جرعة مع انسولين قاعدي مؤقت"; /* Pod state when inserting cannula */ -"Cannula inserting" = "Cannula inserting"; +"Cannula inserting" = "إدخال القنية قيد التنفيذ"; /* String describing a dose that was certainly scheduled */ -"Certain" = "Certain"; +"Certain" = "معتمد"; /* Alert content body for podExpireImminent pod alert */ "Change Pod now. Insulin delivery will stop in 1 hour." = "Change Pod now. Insulin delivery will stop in 1 hour."; @@ -54,13 +54,13 @@ "Change Pod now. Pod has been active for 72 hours." = "Change Pod now. Pod has been active for 72 hours."; /* Format string for invalid message error code (1: error code number) */ -"Command error %1$u" = "Command error %1$u"; +"Command error %1$u" = "خطأ في الأمر %1$u"; /* Status highlight that delivery is uncertain. */ "Comms Issue" = "Comms Issue"; /* Error message when command is rejected because an unacknowledged command is pending. */ -"Communication issue: Unacknowledged command pending." = "Communication issue: Unacknowledged command pending."; +"Communication issue: Unacknowledged command pending." = "مشكلة إتصال: أمر غير معترف به معلق."; /* Description for BeepPreference.manualCommands */ "Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used."; @@ -81,28 +81,28 @@ "Disabled" = "Disabled"; /* Description for Empty reservoir pod fault */ -"Empty reservoir" = "Empty reservoir"; +"Empty reservoir" = "الخزان فارغ"; /* Error message shown when empty response from pod was received */ -"Empty response from pod" = "Empty response from pod"; +"Empty response from pod" = "إجابة فارغة من المضخة"; /* Pod state error event logged shutting down */ -"Error event logged, shutting down" = "Error event logged, shutting down"; +"Error event logged, shutting down" = "تم تسجيل خطأ ، جاري إيقاف التشغيل"; /* Description for expiration alert */ -"Expiration alert" = "Expiration alert"; +"Expiration alert" = "إنذار انتهاء الصلاحية"; /* Title string for BeepPreference.extended */ "Extended" = "Extended"; /* Delivery status when extended bolus is running */ -"Extended bolus running" = "Extended bolus running"; +"Extended bolus running" = "بولوس مطوّل قيد التنفيذ"; /* Delivery status when extended bolus and temp basal is running */ -"Extended bolus running with temp basal" = "Extended bolus running with temp basal"; +"Extended bolus running with temp basal" = "بولوس مطوّل قيد التنفيذ مع انسولين قاعدي مؤقت"; /* Pod state when fault event has occurred */ -"Fault event occurred" = "Fault event occurred"; +"Fault event occurred" = "حدث خطأ"; /* Status highlight that when pod is deactivating. */ "Finish Deactivation" = "Finish Deactivation"; @@ -111,13 +111,13 @@ "Finish Pairing" = "Finish Pairing"; /* Description for finish setup */ -"Finish setup " = "Finish setup "; +"Finish setup " = "الانتهاء من الإعداد "; /* Description for finish setup reminder */ -"Finish setup reminder" = "Finish setup reminder"; +"Finish setup reminder" = "تذكير إنهاء الإعداد"; /* Pod inititialized */ -"Initialized" = "Initialized"; +"Initialized" = "تهيئة"; /* Pod state when inserting cannula */ "Inserting cannula" = "Inserting cannula"; @@ -132,10 +132,10 @@ "Insulin type not configured" = "Insulin type not configured"; /* The format string for Internal pod fault (1: The fault code value) */ -"Internal pod fault %1$03d" = "Internal pod fault %1$03d"; +"Internal pod fault %1$03d" = "خطأ داخلي في المضخة %1$03d"; /* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ -"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@"; +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "البولوس المتعرقل: تم توصيل %1$@ U (من أصل%2$@ U مقرر) %3$@ %4$@ %5$@"; /* Error message for when unexpected address is received (1: received address) (2: expected address) */ "Invalid address 0x%x. Expected 0x%x" = "Invalid address 0x%x. Expected 0x%x"; @@ -153,22 +153,22 @@ "Low Reservoir" = "Low Reservoir"; /* Format string for description for low reservoir advisory (1: reminder units) */ -"Low reservoir advisory (%1$gU)" = "Low reservoir advisory (%1$gU)"; +"Low reservoir advisory (%1$gU)" = "إشعار بأن الخزان منخفض (%1$gU)"; /* Description for low reservoir alarm */ -"Low reservoir advisory alarm" = "Low reservoir advisory alarm"; +"Low reservoir advisory alarm" = "تنبيه بأن الخزان منخفض"; /* Title for RileyLink low battery alert */ -"Low RileyLink Battery" = "Low RileyLink Battery"; +"Low RileyLink Battery" = "بطارية RileyLink منخفضة"; /* Recovery suggestion when no RileyLink is available */ -"Make sure your RileyLink is nearby and powered on" = "Make sure your RileyLink is nearby and powered on"; +"Make sure your RileyLink is nearby and powered on" = "تأكد من أن RileyLink الخاص بك قريب ويعمل"; /* Status highlight when manual temp basal is running. */ "Manual Basal" = "Manual Basal"; /* Pod memory initialized */ -"Memory initialized" = "Memory initialized"; +"Memory initialized" = "تم تهيئة الذاكرة"; /* 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."; @@ -178,13 +178,13 @@ "Multiple Command Alert" = "Multiple Command Alert"; /* Pod alert state when no alerts are active */ -"No alerts" = "No alerts"; +"No alerts" = "لا يوجد تنبيهات"; /* Description for BeepPreference.silent */ "No confidence reminders are used." = "No confidence reminders are used."; /* Description for Fault Event Code .noFaults */ -"No faults" = "No faults"; +"No faults" = "لا توجد أخطاء"; /* Status highlight message for emptyReservoir alarm. Status highlight that a pump is out of insulin. */ @@ -200,29 +200,29 @@ "No pods found" = "No pods found"; /* Error message shown when no response from pod was received */ -"No response from pod" = "No response from pod"; +"No response from pod" = "لايوجد رد من الحجيرة"; /* Error message shown when no response from pod was received */ -"No RileyLink available" = "No RileyLink available"; +"No RileyLink available" = "لا يوجد RileyLink متاح"; /* Delivery status when basal is running Pod state when running above fifty units */ "Normal" = "Normal"; /* Description for MessageError notEnoughData */ -"Not enough data" = "Not enough data"; +"Not enough data" = "لا توجد بيانات كافية"; /* Description for Occlusion detected pod fault */ -"Occlusion detected" = "Occlusion detected"; +"Occlusion detected" = "تم اكتشاف خطأ الاستمالة (Occlusion)"; /* Generic title of the omnipod pump manager */ -"Omnipod" = "Omnipod"; +"Omnipod" = "أومنيبود"; /* Pod status after pairing */ "Paired" = "Paired"; /* Pod status when pairing completed */ -"Pairing completed" = "Pairing completed"; +"Pairing completed" = "إكتمل الإقتران"; /* Description for MessageError parsingError. (1: decription of error), (2: hexadecimal data starting at offset) */ "Parsing Error: %1$@ in (%2$@)" = "Parsing Error: %1$@ in (%2$@)"; @@ -231,7 +231,7 @@ "Please bring only original pod in range or deactivate original pod" = "Please bring only original pod in range or deactivate original pod"; /* Recovery suggestion when no response is received from pod */ -"Please bring your pod closer to the RileyLink and try again" = "Please bring your pod closer to the RileyLink and try again"; +"Please bring your pod closer to the RileyLink and try again" = "من فضلك قم بجعل المضخة أقرب إلى RileyLink ثم حاول مرة أخرى"; /* Alert content body for finishSetupReminder pod alert */ "Please finish pairing your pod." = "Please finish pairing your pod."; @@ -240,25 +240,25 @@ "Please pair a new pod" = "Please pair a new pod"; /* Recovery suggestion when pairing signal strength is too high */ -"Please reposition the RileyLink further from the pod" = "Please reposition the RileyLink further from the pod"; +"Please reposition the RileyLink further from the pod" = "يرجى وضع RileyLink أبعد من الحجيرة"; /* Recovery suggestion when pairing signal strength is too low */ -"Please reposition the RileyLink relative to the pod" = "Please reposition the RileyLink relative to the pod"; +"Please reposition the RileyLink relative to the pod" = "يرجى وضع RileyLink اقرب الى المضخة"; /* Error message shown when user cannot pair because pod is already paired */ "Pod already paired" = "Pod already paired"; /* Error message shown when prime is attempted, but pod is already primed */ -"Pod already primed" = "Pod already primed"; +"Pod already primed" = "تمت تهيأت المضخة مسبقاً"; /* Status highlight message for other alarm. */ "Pod Error" = "Pod Error"; /* Description for expiration advisory alarm */ -"Pod expiration advisory alarm" = "Pod expiration advisory alarm"; +"Pod expiration advisory alarm" = "انذار اقتراب وقت انتهاء صلاحية المضخة"; /* The title for pod expiration notification */ -"Pod Expiration Notice" = "Pod Expiration Notice"; +"Pod Expiration Notice" = "إشعار انتهاء صلاحية المضخة"; /* Description for Pod expired pod fault */ "Pod expired" = "Pod expired"; @@ -267,16 +267,16 @@ "Pod expires in %1$@." = "Pod expires in %1$@."; /* Format string for pod fault code */ -"Pod Fault: %1$@" = "Pod Fault: %1$@"; +"Pod Fault: %1$@" = "خطأ مضخة:%1$@"; /* Error message when cannula insertion fails because the pod is in an unexpected state */ "Pod is not in a state ready for cannula insertion." = "Pod is not in a state ready for cannula insertion."; /* Error message when prime fails because the pod is in an unexpected state */ -"Pod is not in a state ready for priming." = "Pod is not in a state ready for priming."; +"Pod is not in a state ready for priming." = "المضخة ليست في حالة جاهزة للتهيئة."; /* Error message action could not be performed because pod is suspended */ -"Pod is suspended" = "Pod is suspended"; +"Pod is suspended" = "تم تعليق المضخة"; /* Status highlight message for occlusion alarm. */ "Pod Occlusion" = "Pod Occlusion"; @@ -288,23 +288,23 @@ "Pod sent ack instead of response" = "Pod sent ack instead of response"; /* Pod state when prime or cannula insertion has not completed in the time allotted */ -"Pod setup window expired" = "Pod setup window expired"; +"Pod setup window expired" = "تم تجاوز الوقت المسموح لإعداد المضخة"; /* Description for pod suspended reminder */ -"Pod suspended reminder" = "Pod suspended reminder"; +"Pod suspended reminder" = "تذكير بأن المضخة معلقة"; /* Format string for poor pod signal strength */ -"Poor signal strength" = "Poor signal strength"; +"Poor signal strength" = "قوة الإشارة ضعيفة"; /* Delivery status when pod is priming Pod status when priming */ -"Priming" = "Priming"; +"Priming" = "قيد التمهيد"; /* Pod state when priming completed */ -"Priming completed" = "Priming completed"; +"Priming completed" = "اكتمل التمهيد"; /* Pod state when ready for basal programming */ -"Ready for basal programming" = "Ready for basal programming"; +"Ready for basal programming" = "جاهز لبرمجة الانسولين القاعدي"; /* Pod state when ready for cannula insertion */ "Ready to insert cannula" = "Ready to insert cannula"; diff --git a/Dependencies/OmniKit/OmniKitUI/Resources/ar.lproj/Localizable.strings b/Dependencies/OmniKit/OmniKitUI/Resources/ar.lproj/Localizable.strings index 8981493221..77e9fe2942 100644 --- a/Dependencies/OmniKit/OmniKitUI/Resources/ar.lproj/Localizable.strings +++ b/Dependencies/OmniKit/OmniKitUI/Resources/ar.lproj/Localizable.strings @@ -2,10 +2,10 @@ "—" = "—"; /* Format string for last status date on pod details screen */ -"%@ ago" = "%@ ago"; +"%@ ago" = "منذ ‭%@"; /* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; +"%@ dB" = "%@ ديسيبل"; /* No comment provided by engineer. */ "%@ has recovered communication with the pod on your body.\n\nInsulin delivery records have been updated and should match what has actually been delivered.\n\nYou may continue to use %@ normally now." = "%@ has recovered communication with the pod on your body.\n\nInsulin delivery records have been updated and should match what has actually been delivered.\n\nYou may continue to use %@ normally now."; @@ -78,13 +78,13 @@ "Adjusting Pump Time..." = "Adjusting Pump Time..."; /* The title of the cell showing alarm status */ -"Alarms" = "Alarms"; +"Alarms" = "التنبيهات"; /* Alert title for cancel pairing modal */ "Are you sure you want to cancel Pod setup?" = "Are you sure you want to cancel Pod setup?"; /* Confirmation message for shutting down a pod */ -"Are you sure you want to shutdown this pod?" = "Are you sure you want to shutdown this pod?"; +"Are you sure you want to shutdown this pod?" = "هل أنت متأكد من أنك تريد إيقاف هذه المضخة؟"; /* No comment provided by engineer. */ "Are you sure you want to skip Omnipod Onboarding?" = "Are you sure you want to skip Omnipod Onboarding?"; @@ -102,7 +102,7 @@ "Attemping to re-establish communication" = "Attemping to re-establish communication"; /* Back button text on DeliveryUncertaintyRecoveryView */ -"Back" = "Back"; +"Back" = "رجوع"; /* The title of the cell showing pod basal status */ "Basal Delivery" = "Basal Delivery"; @@ -129,10 +129,10 @@ "Change Pod now. Insulin delivery will stop in %1$@ or when no more insulin remains." = "Change Pod now. Insulin delivery will stop in %1$@ or when no more insulin remains."; /* The title of the command to change pump time zone */ -"Change Time Zone" = "Change Time Zone"; +"Change Time Zone" = "تغيير المنطقة الزمنية"; /* Progress message for changing pod time. */ -"Changing time…" = "Changing time…"; +"Changing time…" = "تغيير الوقت…"; /* Title for check cannula screen */ "Check Cannula" = "Check Cannula"; @@ -147,7 +147,7 @@ "Checking..." = "Checking..."; /* Title for uncertainty recovered screen */ -"Comms Recovered" = "Comms Recovered"; +"Comms Recovered" = "تم استرداد الاتصال"; /* Text for confidence reminders navigation link */ "Confidence Reminders" = "Confidence Reminders"; @@ -218,7 +218,7 @@ "Device Information" = "Device Information"; /* Header for devices section of RileyLinkSetupView */ -"Devices" = "Devices"; +"Devices" = "الأجهزة"; /* Title text for button to disable bolus beeps */ "Disable Bolus Beeps" = "Disable Bolus Beeps"; @@ -228,7 +228,7 @@ "Discard Pod" = "Discard Pod"; /* No comment provided by engineer. */ -"Done" = "Done"; +"Done" = "تمّ"; /* Title text for button to enable bolus beeps */ "Enable Bolus Beeps" = "Enable Bolus Beeps"; @@ -255,10 +255,10 @@ "Expiration Reminder Default" = "Expiration Reminder Default"; /* The title of the cell showing the pod expiration after expiry */ -"Expired" = "Expired"; +"Expired" = "منتهي"; /* The title of the cell showing the pod expiration */ -"Expires" = "Expires"; +"Expires" = "الانتهاء"; /* Alert title for failing to cancel manual basal error */ "Failed to Cancel Manual Basal" = "Failed to Cancel Manual Basal"; @@ -276,7 +276,7 @@ "Failed to update confidence reminder preference." = "Failed to update confidence reminder preference."; /* Alert title for error when updating expiration reminder */ -"Failed to Update Expiration Reminder" = "Failed to Update Expiration Reminder"; +"Failed to Update Expiration Reminder" = "فشل في تحديث تذكير انتهاء صلاحية"; /* Alert title for error when updating low reservoir reminder */ "Failed to Update Low Reservoir Reminder" = "Failed to Update Low Reservoir Reminder"; @@ -385,7 +385,7 @@ "minute" = "minute"; /* Unit for plural minutes in pod life remaining */ -"minutes" = "minutes"; +"minutes" = "دقائق"; /* Alert title for missing temp basal configuration */ "Missing Config" = "Missing Config"; @@ -399,7 +399,7 @@ "Next" = "Next"; /* Button label for user to answer cannula was not properly inserted */ -"No" = "No"; +"No" = "لا"; /* Text shown in insulin remaining space when no pod is paired */ "No\nDelivery" = "No\nDelivery"; @@ -425,7 +425,7 @@ /* navigation title for notification settings Text for pod details disclosure row */ -"Notification Settings" = "Notification Settings"; +"Notification Settings" = "إعدادات الإشعارات"; /* No comment provided by engineer. */ "Numbers" = "Numbers"; @@ -547,7 +547,7 @@ "Pump Time" = "Pump Time"; /* Label text for basal rate summary */ -"Rate" = "Rate"; +"Rate" = "معدّل"; /* Label describing time remaining view */ "Remaining" = "Remaining"; @@ -593,14 +593,14 @@ /* Title of button to save delivery limit settings Title of button to sync basal profile when no pod paired */ -"Save" = "Save"; +"Save" = "إحفظ"; /* button title for saving low reservoir reminder while saving button title for saving scheduled reminder while saving */ "Saving..." = "Saving..."; /* The detail text of the basal row when pod is running scheduled basal */ -"Schedule" = "Schedule"; +"Schedule" = "الجدول"; /* Title of insulin delivery section */ "Scheduled Basal" = "Scheduled Basal"; @@ -719,7 +719,7 @@ /* Label for expiration reminder row Label for scheduled expiration reminder row Label for scheduled reminder value row */ -"Time" = "Time"; +"Time" = "الوقت"; /* Title for pod sync time action sheet. title for time change detected notice */ @@ -763,7 +763,7 @@ "Wait until insertion is completed." = "Wait until insertion is completed."; /* Button label for user to answer cannula was properly inserted */ -"Yes" = "Yes"; +"Yes" = "نعم"; /* Button title for confirm deactivation option */ "Yes, Deactivate Pod" = "Yes, Deactivate Pod"; diff --git a/Dependencies/OmniKit/OmniKitUI/Resources/vi.lproj/Localizable.strings b/Dependencies/OmniKit/OmniKitUI/Resources/vi.lproj/Localizable.strings index ccfca0225a..685b3eedd5 100644 --- a/Dependencies/OmniKit/OmniKitUI/Resources/vi.lproj/Localizable.strings +++ b/Dependencies/OmniKit/OmniKitUI/Resources/vi.lproj/Localizable.strings @@ -657,7 +657,7 @@ "Suspending insulin delivery..." = "Đang tạm dừng liều insulin..."; /* Title text for the button to delete Omnipod PumpManager */ -"Switch from Omnipod Pumps" = "Chuyển đổ từ bơm Omnipod"; +"Switch from Omnipod Pumps" = "Chuyển đổi từ bơm Omnipod"; /* Label for PumpManager deletion button */ "Switch to other insulin delivery device" = "Chuyển đổi sang bơm khác"; diff --git a/Dependencies/rileylink_ios/RileyLinkKitUI/ar.lproj/Localizable.strings b/Dependencies/rileylink_ios/RileyLinkKitUI/ar.lproj/Localizable.strings index f74714bf35..72c1419928 100644 --- a/Dependencies/rileylink_ios/RileyLinkKitUI/ar.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/RileyLinkKitUI/ar.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; +"%@ dB" = "%@ ديسيبل"; /* Unit format string for an value in percent */ "%@%%" = "%@%%"; @@ -29,7 +29,7 @@ "Connection Monitoring" = "Connection Monitoring"; /* The title of the cell showing BLE connection state */ -"Connection State" = "Connection State"; +"Connection State" = "حالة الإتصال"; /* The title of the cell for connection vibration */ "Connection Vibration" = "Connection Vibration"; @@ -38,7 +38,7 @@ "Device" = "Device"; /* The title of the devices table section in RileyLink settings */ -"Devices" = "Devices"; +"Devices" = "الأجهزة"; /* The disconnected state */ "Disconnected" = "Disconnected"; @@ -50,7 +50,7 @@ "Find Device" = "Find Device"; /* The title of the cell showing firmware version */ -"Firmware" = "Firmware"; +"Firmware" = "البرنامج الثابت"; /* The title of the cell showing current rileylink frequency */ "Frequency" = "Frequency"; @@ -71,7 +71,7 @@ "Low Battery Alert" = "Low Battery Alert"; /* The title of the cell showing device name */ -"Name" = "Name"; +"Name" = "الاسم"; /* Detail text when battery alert disabled. Text indicating LED Mode is off */ diff --git a/Dependencies/rileylink_ios/RileyLinkKitUI/vi.lproj/Localizable.strings b/Dependencies/rileylink_ios/RileyLinkKitUI/vi.lproj/Localizable.strings index 2cff4cd87f..05cebbbca6 100644 --- a/Dependencies/rileylink_ios/RileyLinkKitUI/vi.lproj/Localizable.strings +++ b/Dependencies/rileylink_ios/RileyLinkKitUI/vi.lproj/Localizable.strings @@ -88,10 +88,10 @@ /* The title of the section for orangelink commands The title of the section for rileylink commands */ -"Test Commands" = "Thử nghiệm câu lệnh"; +"Test Commands" = "Kiểm tra câu lệnh"; /* The title of the cell showing Test Vibration */ -"Test Vibration" = "Thử nghiệm độ rung"; +"Test Vibration" = "Kiểm tra độ rung"; /* The title of the command to update diagnostic LEDs */ "Toggle Diagnostic LEDs" = "Cho phép chuẩn đoán LEDs"; diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index fcc7422dcd..344710a6ac 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -47,6 +47,8 @@ 1982F7DB2BA6509D00EAFADE /* PumpStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1982F7DA2BA6509D00EAFADE /* PumpStorage.swift */; }; 198377D2266BFFF6004DE65E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; }; 198CED9D2B2E62940073032D /* OverrideStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 198CED9C2B2E62940073032D /* OverrideStorage.swift */; }; + 1994DBE02C696A410076FCF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 19DA487F29CD2B8400EEA1E7 /* Assets.xcassets */; }; + 1994DBE12C696FA10076FCF1 /* 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 /* Configs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A910352A24D6D700C8951B /* Configs.swift */; }; @@ -2889,6 +2891,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1994DBE02C696A410076FCF1 /* Assets.xcassets in Resources */, + 1994DBE12C696FA10076FCF1 /* Localizable.strings in Resources */, 6B1A8D242B14D91700E76752 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved index 37e3510a58..c6fc96a667 100644 --- a/FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -39,7 +39,7 @@ }, { "package": "SwiftCharts", - "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts.git", + "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts", "state": { "branch": "master", "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2", diff --git a/FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/Contents.json b/FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/Contents.json new file mode 100644 index 0000000000..501759a433 --- /dev/null +++ b/FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "glucoesDropClearNoPointerExtraWhite.png", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "glucoesDropBlack5_2.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "compression-type" : "lossless", + "template-rendering-intent" : "original" + } +} diff --git a/FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/glucoesDropBlack5_2.png b/FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/glucoesDropBlack5_2.png new file mode 100644 index 0000000000000000000000000000000000000000..d19eedff4d4aa68893319a9146b99f3fec8add0c GIT binary patch literal 36502 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 Bc$xqJ literal 0 HcmV?d00001 diff --git a/FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/glucoesDropClearNoPointerExtraWhite.png b/FreeAPS/Resources/Assets.xcassets/glucoseDrops.imageset/glucoesDropClearNoPointerExtraWhite.png new file mode 100644 index 0000000000000000000000000000000000000000..f48d5712f78a391435c31ff190dbf43a1abac931 GIT binary patch literal 34090 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

o9PE^HWZnGO7Oa3; z{k*b~RGcYj9g9*BJk1IN1P7$1MG?rlp(P!o+9X*sWTwo_SYPqJ4+ydril6he7NXhw zd*H>3(XC=7vygJ30s*TghzNk`C!Z_OnH8<~29-YH9`m|XKx_`#wS%A>hYw*i%a5W2 z;cef*Z`y!c+zxEKOFDXV9MT6}pAiRMcMXqsgJ zFZB_`s$q@EZBc(?V?(HBgp~Ytf2B@1y~V-h z)202^Av;mwBps(TL`i&=t~7v^$|PdN4oT)=1nkhx6&%>O0|g!*~S zN}x>_e}#zYxh#l(V?-b)9wwI;=f(r_I|oE{iVuCvF|?UiMNS@3598aRzWSXQJ@W~7 zfuE-_+eauFhw8wu&}TF~!>UOsWf9X=-{@=S_K%KQjceoQa`pqPNJ;J(F$PWNX++Lc zegR}1Xc$J2st0}iSdi1|wbaxuI8rD6OAsR&&!oGgNC*g)zSqy*!n;5E+ysn9kkm8y zxeSv+BLjf?^n>+m^QJ@77huK(0s?t9!Di+ov9iw4mqZNbDqCw}Rs(66col0Ec+m1@n29iLp8t#=CN!!!D zXNLn;vmKJ-1oAz!JX8(;%L*ZKt0r`%2KJFw}; zL^XrKwCoq#{ui(Z?>H{a(_Y})r4;qL#~9N6SeQ_-5b&DIQ8S%?PDJ(`6-_$QAd)+| z*hpz38QruSY#Cdt zI;;Jmw5|R+oc@a+*{^}z8V^7+)mw%?C<2#g13JWyX8;2fLcd!;T~3gguuiG)x1;<8 zrF&?=ReLZxI0z9sQ=iv)W>`GxF2hk1eDV{18^5{|xl6n(-2=pMfqTo_W;SdGm0R#r z2-=kfv?K~~Oy2u));ZFAxqx_zPip^uqq1;p~*GCSlURB974dW}lEu;=tEWG; zpelq;D=SXS4XWIpynBPLiR!Injz_c^78F~!yBszHYD%C_Pg7GBaqUZ`+S3TI$rI~)aRY3oJpWJ)T+9|g$+KzY&xyh ze?faBJ`t9yI4uY~$-6lUg%_cWXj z)(C5O?=WQC0vsy1LCG@fwDCD@fO%h-zDoogs~pD1=aipB=%Wz4=?=UZ`+M3^1~k8fFY&K&ccYt#IrLOMesAxS5*gi-D&C+lK7SA!;ObWAwX@ z6cpNr!1=UcM*N*JT^u814`0TWLlXYxgdiW4tb~dcVgSy!agz2-O@=33 z`oUgzjZs`lHOwj>jRilr^j#opqmNiS_^5q29Du9c$V(w2A0XC&0qTm)zETH{tmG+e zPC+;Xpi^VCtePjp{h;R};T4TtA|=_sM1WyETznVBfwjk){L5RFP4S@g)`!Lr^If>; za>o^$7|j4QaAcUE*n!DTvpSH`jpDA=AugaTq`1S0Ybh^=aU>G?T~M8*F6Bcx7&bFG zHl{U1Qc3AL6(rUL{Y8u09+nHnrL-Q5=^GnV(&jYsgEQ&v68mYPK0Nu!Qk0GG1Scwi z$zw)gA>l)tIKU5$DAC|sL&x~_a}0XU{r+AlO5>*uXkZ&8ffyEerntw?d^P+fdF50< z+EHEDz0=3rTW6GGrStBlWLu&b^$6UvQZu15m?$&@?e`IH= zbN;72WiUYjD2CR^nAm0A0=!613j7! zJGV8MJ2EyBC^ykPdt!Wb&#N7gUecHVfuOuy&-j3cXWs_)-6zi>(V(KUjqTCnYKQq2 z))P;LT7(lwy?sv#kU}E9ELLccztowX3=PF<#5w3dF$0?(oC$^h&)^QEoC!$L^9!7a z(Cnmbz=dHmjGV_05G=cNe%t zBuoIyqkAaiawR3;5gJ}>bo47FYwX&*DcFfseVsPxdD>7Y(yGvJksP=)VF5NBMgY*G z*XkF+>_3|fbx0_~XT|;5`FQ}L2@PS4|C`ETcVyixcSRV<5_qjWs&9A6n+{< z(A9w6H-9s{k`)ES4PW_f;^NZ=!~Mkj!OukDWy8GfawjV!9q;WMNxfAIrDpe$HFqnM(R2{Jc( z4I9$bG-I$8E~?%SE^W%A(J4dDyaVz` zTH@<?d{V-JMjLQPR74eb_E-18>v_9;gZm3(uDH{Vp^@uC-UVgwXX}BJOwe56xzEYWaTy zbdj0BBCXOtyDX#w>TbzEKQST{bJ)-v$%zA5Sq$>B+im|+NmFgr^j zSaQbVi?}~GP#h40QpR~BB`$iY1b`t&Oh0oP(wT!5eCem1jr|Ko*UV>jQVL4h&&7%T zJz`K{P%Sd}`|@Dr6Uf(IeoLgSp^2vhUyXVnq&x^KNbhJae71d0+F3tGP(2(oVnzT_ zp=b>yksp~t0D?-r#Y4@gF~1z1U^!V@5LkRc@;M$C%mA&)W_xptHTzpMrIV#JhMKN+ zrt?9wu_D4MXRKvDo#!x4LH6_^Q%nh!tx+;+c-733@>UUz*efV846#3XgmTn&^bS&j%_)k z_jR{E(m7iS3JS^|cyK&@dPj_g4)|#}h3bvQ^OMb97jvaDI+0^p<36GvZJ_fy=Xd*o zRp|t}%1=b2f{7kEGe7>~li(iZSHnN0TnVq&I@6>|v8AdGi<|l{3BC&kf|M=o`DBzC zl{8^B9}`(gvdN{VzrQGZ%~sdswQM zeI*&cLfZhqH3Wief*_=ATrwo8?mE04EA-Dlm{e9@w0$hmbUX06yAQNBYhDexlO;K* znyRZWE+}t#pUY9q+_5f1pu*rHw|sWEc8naOT+p1)Ws9tS+QC}4rGc<*mq#|0z^VgrxTR|mDGD7dk319}I z>=Plvypj^a1?-%WVWKfB;e^%*b;iKxQaW8dabcCCoFW-{bn~ZO zd2p@;+<06PMw_zLUSn^IGxN;4cUc?PU`jXhOKN(so#Hy8l*2< zKHE0Q?M%cTB%YUq8dyl9_*Ot;V%)Z>NFz+-nT*D0LlLXFqjR&2H>8jRVu+mkr z2Y(ddRvhs&^x*ef17wew>{}p&q=`qd*|~FA!!QJiK(;))}@fz;fOZ}WZ2=CIU5!BC1#X7LzBlt;?C58he;9KOSBcjV8P8Q z(Wsx(H>9rD z*>`6@WF`$D#Ww6C+5X@c^A%nECdu3wOLMn*hGY2CB)|QEDm(ROq9X@Y>$3T2Yadke zWF=~Sv5cJJ2ri87PTwr90AT9#E}zr6uM0%acoarVPHLLfOwLA1;R0H2HZCTc`NV?P zby$HC#4P8bRRix8SDd9$S=h^@&kpLpD$9y%9iLE?lU5Y*gcY6IYKI&J4Ds_#CmmrB z7W{f0t5DVZ$H9T|(d#tB&J1km)%dnIAX>d5!lxKM$UtQY<65t@Vi;Wc5{dn? zz#O)3TsO@b&(g^YlYkl@^yHK?8BKFy7qwK&hw!d%c?VUVTH~(^#tFb8mJ|LF&AHst z(k(k%w+ovEvfl=II*fJe*&f07bQE7RyK9dxBXjDOV_1N8NExrSgo^*r!=A&-go{$y zfUkaf^(}j+E|F+u5n7Rj?#pdPld{m(}&#P*SUnTt300 ze7aq$TdV8?7F`4809f<@FYB{Q$Nb{c?WW zZ21#~gWcYXPv=#a2j4b6jb=Lx-?{%%uI-z$C<~YTNQ?2u+YY_u*6AP-f6Lb zpcmwWOrg=Sv3{-g(yi*Mjh5{BBTSGTpfzg@G^)0VUv3>7#e$rQe|ew91TH8!$${{W zP~-zGfXRR|(UYRyah7zS5G&&`SL?FxkX-Aoe9m38;aW8ilBb~N0qH{JeqHDr7H?_A zE`U@}E=SVKg+G?PkR)Zn_anjFZ?}9_u#~^=BVE= zo)~HhAM2nuNRfjxrUE+Qf$C#rd4w+Q;;$}oq)AlY>H0clPyEg8Mk!w~ec;ZY%S#UN zvl=3|%u`4g@WJ<3flJsL)_zGYk#xY12fou;h@a&WGf9I30)~v$7Jx#JgAWF5=e=;qWjTt zaoG;P#Big~0)1i7+Ox&o7Z|gQ<#lzQd_NEEf;Jyp$3YurH7}o2BJB~q9vm}&dCyj! zgWBJ;q;arsYaJWrZ}D)vn#&v>$+eD#oPP$rA7ZuL{UaVYY&Rv zQWLMT1kNJP>`SwK9<_cSw*4a^o1Cwe*|lL;xq-C(<62LKJOQIM%WQbjG8x4X^pKG0 zSeD@D1Xf{UrSHz3cOa)>O!<{kwZ;Q*8AE(ql9%p$rZun5k;zrc0Qu0jEbOvW-nUW* zfx3nVI8IZ!@hM=y$#ryf-}d^mx4tcRW->kUtns5QL29lR6Q?}LaAyxZ?6+&}6%F8e z;eP0Bd7$P1B`9bF8-9enCK`AT`lGk^KuJldIbpJXoY=-lVZv8pqD1^B-o#g7(?h+r zbKjJbS0bXK{!U;{;ua?LR1b`R#?csTVhfL-SU{U8jA4Jjq_h-VqFAq_I?r1l2!erd z58JiUgmcoLat7Ly5ZE_XtbKci_zWS6@*W=_H|~2Bru`Vqdg~aFvhcXy`z*V^0Iz6t zZZlU)P}i(H`Skpa-g*4XTgG?r@r@PEYmP+vMbI%IQ>+WzvFwoLg2;pDp=6?Q&Yb2hf|+j^y4)ycii(f-dGM*x6z8CX!~ zYD;Gx3nwH1D&5f*R z>`bxJ^3`ViIXIYUPDd*5oPH=P+p-m}5vd+g$AD~-64-AYIL4t^-(To{R);kWTR(<1 z_A|FBz3^l_PU%TVUeLd=8i?4!V+^b|a4rfN&lGYNEP+h)RuP%__oeluGW>{%j{ci< zW)Fc9SS0~*C~&@a2OZ;#&?v;V*x~GOdXFCRdj^CG?Cjx~gfwCS tXyELi1poV=|J{WDm4yGVwV=RY!FV_y>eiaNU4{n!$xA<%DwQw^`akpTEG+;4 literal 0 HcmV?d00001 diff --git a/FreeAPS/Resources/javascript/bundle/profile.js b/FreeAPS/Resources/javascript/bundle/profile.js index 03366d75e8..b932cef25f 100644 --- a/FreeAPS/Resources/javascript/bundle/profile.js +++ b/FreeAPS/Resources/javascript/bundle/profile.js @@ -1,2 +1 @@ -/*! For license information please see profile.js.LICENSE.txt */ -var freeaps_profile;(()=>{var t={6261:(t,n)=>{t.exports=function(t){var n=new Date;return n.setHours("00"),n.setMinutes("00"),n.setSeconds("00"),n.getTime()+60*t*1e3}},1836:(t,n,r)=>{var e=r(6486);n.maxDailyBasal=function(t){var n=e.maxBy(t.basals,(function(t){return Number(t.rate)}));return 1e3*Number(n.rate)/1e3},n.maxBasalLookup=function(t){return t.settings.maxBasal},n.basalLookup=function(t,n){var r=n;void 0===n&&(r=new Date);var u=e.sortBy(t,(function(t){return t.i})),i=u[u.length-1].rate;if(0!==i){for(var a=60*r.getHours()+r.getMinutes(),o=0;o=u[o].minutes&&a{var e=r(6261);function u(t,n){var r=new Date,u=t.carbratio;if(void 0===u||void 0===u.schedule);else{var i;if("grams"===u.units||"exchanges"===u.units){i=u.schedule[u.schedule.length-1];for(var a=0;a=e(u.schedule[a].offset)&&r150)return void console.error("Error: carbRatio of "+i+" out of bounds.");break}return"exchanges"===u.units&&(i.ratio=12/i.ratio),i.ratio}console.error("Error: Unsupported carb_ratio units "+u.units)}}u.carbRatioLookup=u,t.exports=u},6014:(t,n,r)=>{var e=r(1836),u=r(4034),i=r(3725),a=r(5364),o=r(6486);function f(){return{max_iob:9,max_daily_safety_multiplier:5,current_basal_safety_multiplier:6,autosens_max:2.5,autosens_min:.5,rewind_resets_autosens:!0,high_temptarget_raises_sensitivity:!1,low_temptarget_lowers_sensitivity:!1,sensitivity_raises_target:!1,resistance_lowers_target:!1,exercise_mode:!1,half_basal_exercise_target:150,maxCOB:120,skip_neutral_temps:!1,unsuspend_if_no_temp:!1,min_5m_carbimpact:8,autotune_isf_adjustmentFraction:1,remainingCarbsFraction:1,remainingCarbsCap:90,enableUAM:!0,A52_risk_enable:!1,enableSMB_with_COB:!0,enableSMB_with_temptarget:!0,enableSMB_always:!0,enableSMB_after_carbs:!0,allowSMB_with_high_temptarget:!0,maxSMBBasalMinutes:90,maxUAMSMBBasalMinutes:90,SMBInterval:3,bolus_increment:.05,maxDelta_bg_threshold:.2,curve:"rapid-acting",useCustomPeakTime:!1,insulinPeakTime:45,carbsReqThreshold:1,offline_hotspot:!1,noisyCGMTargetMultiplier:1.3,suspend_zeros_iob:!0,enableEnliteBgproxy:!1,calc_glucose_noise:!1,target_bg:!1,smb_delivery_ratio:.5,adjustmentFactor:1,useNewFormula:!1,enableDynamicCR:!1,sigmoid:!1,weightPercentage:.65,tddAdjBasal:!1,enableSMB_high_bg:!1,enableSMB_high_bg_target:110}}function c(t,n){var r=n&&n.type?n:{max_iob:9,max_daily_safety_multiplier:5,current_basal_safety_multiplier:6,autosens_max:2.5,autosens_min:.5,rewind_resets_autosens:!0,high_temptarget_raises_sensitivity:!1,low_temptarget_lowers_sensitivity:!1,sensitivity_raises_target:!1,resistance_lowers_target:!1,exercise_mode:!1,half_basal_exercise_target:150,maxCOB:120,skip_neutral_temps:!1,unsuspend_if_no_temp:!1,min_5m_carbimpact:8,autotune_isf_adjustmentFraction:1,remainingCarbsFraction:1,remainingCarbsCap:90,enableUAM:!0,A52_risk_enable:!1,enableSMB_with_COB:!0,enableSMB_with_temptarget:!0,enableSMB_always:!0,enableSMB_after_carbs:!0,allowSMB_with_high_temptarget:!0,maxSMBBasalMinutes:90,maxUAMSMBBasalMinutes:90,SMBInterval:3,bolus_increment:.05,maxDelta_bg_threshold:.2,curve:"rapid-acting",useCustomPeakTime:!1,insulinPeakTime:45,carbsReqThreshold:1,offline_hotspot:!1,noisyCGMTargetMultiplier:1.3,suspend_zeros_iob:!0,enableEnliteBgproxy:!1,calc_glucose_noise:!1,target_bg:!1,smb_delivery_ratio:.5,adjustmentFactor:1,useNewFormula:!1,enableDynamicCR:!1,sigmoid:!1,weightPercentage:.65,tddAdjBasal:!1,enableSMB_high_bg:!1,enableSMB_high_bg_target:110,threshold_setting:65};for(var f in r)t.hasOwnProperty(f)&&(r[f]=t[f]);var c=t.settings;if(!(t.settings.insulin_action_curve>1))return console.error("DIA of",r.dia,"is not supported"),-1;if(r.dia=c.insulin_action_curve,t.model&&(r.model=t.model),r.skip_neutral_temps=t.skip_neutral_temps,r.current_basal=e.basalLookup(t.basals),r.basalprofile=t.basals,o.forEach(r.basalprofile,(function(t){t.rate=+(Math.round(t.rate+"e+3")+"e-3")})),r.max_daily_basal=e.maxDailyBasal(t),r.max_basal=e.maxBasalLookup(t),0===r.current_basal)return console.error("current_basal of",r.current_basal,"is not supported"),-1;if(0===r.max_daily_basal)return console.error("max_daily_basal of",r.max_daily_basal,"is not supported"),-1;if(r.max_basal<.1)return console.error("max_basal of",r.max_basal,"is not supported"),-1;var l=u.bgTargetsLookup(t,r);return r.out_units=t.targets.user_preferred_units,r.min_bg=Math.round(l.min_bg),r.max_bg=Math.round(l.max_bg),r.bg_targets=t.targets,o.forEach(r.bg_targets.targets,(function(t){t.high=Math.round(t.high),t.low=Math.round(t.low),t.min_bg=Math.round(t.min_bg),t.max_bg=Math.round(t.max_bg)})),delete r.bg_targets.raw,r.temptargetSet=l.temptargetSet,r.sens=i.isfLookup(t.isf),r.isfProfile=t.isf,r.sens<5?(console.error("ISF of",r.sens,"is not supported"),-1):(void 0!==t.carbratio?(r.carb_ratio=a.carbRatioLookup(t,r),r.carb_ratios=t.carbratio):console.error("Profile wasn't given carb ratio data, cannot calculate carb_ratio"),r)}c.defaults=f,c.displayedDefaults=function(){var t={max_iob:9,max_daily_safety_multiplier:5,current_basal_safety_multiplier:6,autosens_max:2.5,autosens_min:.5,rewind_resets_autosens:!0,high_temptarget_raises_sensitivity:!1,low_temptarget_lowers_sensitivity:!1,sensitivity_raises_target:!1,resistance_lowers_target:!1,exercise_mode:!1,half_basal_exercise_target:150,maxCOB:120,skip_neutral_temps:!1,unsuspend_if_no_temp:!1,min_5m_carbimpact:8,autotune_isf_adjustmentFraction:1,remainingCarbsFraction:1,remainingCarbsCap:90,enableUAM:!0,A52_risk_enable:!1,enableSMB_with_COB:!0,enableSMB_with_temptarget:!0,enableSMB_always:!0,enableSMB_after_carbs:!0,allowSMB_with_high_temptarget:!0,maxSMBBasalMinutes:90,maxUAMSMBBasalMinutes:90,SMBInterval:3,bolus_increment:.05,maxDelta_bg_threshold:.2,curve:"rapid-acting",useCustomPeakTime:!1,insulinPeakTime:45,carbsReqThreshold:1,offline_hotspot:!1,noisyCGMTargetMultiplier:1.3,suspend_zeros_iob:!0,enableEnliteBgproxy:!1,calc_glucose_noise:!1,target_bg:!1,smb_delivery_ratio:.5,adjustmentFactor:1,useNewFormula:!1,enableDynamicCR:!1,sigmoid:!1,weightPercentage:.65,tddAdjBasal:!1,enableSMB_high_bg:!1,enableSMB_high_bg_target:110,threshold_setting:65},n={};return n.max_iob=t.max_iob,n.max_daily_safety_multiplier=t.max_daily_safety_multiplier,n.current_basal_safety_multiplier=t.current_basal_safety_multiplier,n.autosens_max=t.autosens_max,n.autosens_min=t.autosens_min,n.rewind_resets_autosens=t.rewind_resets_autosens,n.exercise_mode=t.exercise_mode,n.sensitivity_raises_target=t.sensitivity_raises_target,n.unsuspend_if_no_temp=t.unsuspend_if_no_temp,n.enableSMB_with_COB=t.enableSMB_with_COB,n.enableSMB_with_temptarget=t.enableSMB_with_temptarget,n.enableUAM=t.enableUAM,n.curve=t.curve,n.offline_hotspot=t.offline_hotspot,n.bolus_increment=t.bolus_increment,n.smb_delivery_ratio=t.smb_delivery_ratio,n.maxDelta_bg_threshold=t.maxDelta_bg_threshold,n.adjustmentFactor=t.adjustmentFactor,n.useNewFormula=t.useNewFormula,n.enableDynamicCR=t.enableDynamicCR,n.sigmoid=t.sigmoid,n.weightPercentage=t.weightPercentage,n.tddAdjBasal=t.tddAdjBasal,console.error(n),n},t.exports=c},3725:(t,n,r)=>{var e=r(6486),u=null;function i(t,n){var r=n;void 0===n&&(r=new Date);var i=60*r.getHours()+r.getMinutes();if(u&&i>=u.offset&&i=c.offset&&i{var e=r(6261);function u(t,n){return a(i(t,n))}function i(t,n){for(var r=t.targets,u=t.temptargets,i=new Date,a=r.targets[r.targets.length-1],o=0;o=e(r.targets[o].offset)&&i=c&&0===u[o].duration){f=a;break}if(!u[o].targetBottom||!u[o].targetTop){console.error("eventualBG target range invalid: "+u[o].targetBottom+"-"+u[o].targetTop);break}if(i>=c&&i"']/g,J=RegExp(H.source),Y=RegExp(V.source),Q=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,tt=/<%=([\s\S]+?)%>/g,nt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,rt=/^\w*$/,et=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,ut=/[\\^$.*+?()[\]{}|]/g,it=RegExp(ut.source),at=/^\s+/,ot=/\s/,ft=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,ct=/\{\n\/\* \[wrapped with (.+)\] \*/,lt=/,? & /,st=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,_t=/[()=,{}\[\]\/\s]/,ht=/\\(\\)?/g,pt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,gt=/\w*$/,vt=/^[-+]0x[0-9a-f]+$/i,dt=/^0b[01]+$/i,yt=/^\[object .+?Constructor\]$/,bt=/^0o[0-7]+$/i,mt=/^(?:0|[1-9]\d*)$/,wt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,xt=/($^)/,St=/['\n\r\u2028\u2029\\]/g,Bt="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",jt="\\u2700-\\u27bf",At="a-z\\xdf-\\xf6\\xf8-\\xff",kt="A-Z\\xc0-\\xd6\\xd8-\\xde",It="\\ufe0e\\ufe0f",Mt="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",Ct="['’]",Ft="[\\ud800-\\udfff]",Ot="["+Mt+"]",Rt="["+Bt+"]",Et="\\d+",zt="[\\u2700-\\u27bf]",Tt="["+At+"]",Dt="[^\\ud800-\\udfff"+Mt+Et+jt+At+kt+"]",Lt="\\ud83c[\\udffb-\\udfff]",Ut="[^\\ud800-\\udfff]",Wt="(?:\\ud83c[\\udde6-\\uddff]){2}",Pt="[\\ud800-\\udbff][\\udc00-\\udfff]",Nt="["+kt+"]",$t="(?:"+Tt+"|"+Dt+")",qt="(?:"+Nt+"|"+Dt+")",Gt="(?:['’](?:d|ll|m|re|s|t|ve))?",Zt="(?:['’](?:D|LL|M|RE|S|T|VE))?",Kt="(?:"+Rt+"|"+Lt+")"+"?",Ht="[\\ufe0e\\ufe0f]?",Vt=Ht+Kt+("(?:\\u200d(?:"+[Ut,Wt,Pt].join("|")+")"+Ht+Kt+")*"),Jt="(?:"+[zt,Wt,Pt].join("|")+")"+Vt,Yt="(?:"+[Ut+Rt+"?",Rt,Wt,Pt,Ft].join("|")+")",Qt=RegExp(Ct,"g"),Xt=RegExp(Rt,"g"),tn=RegExp(Lt+"(?="+Lt+")|"+Yt+Vt,"g"),nn=RegExp([Nt+"?"+Tt+"+"+Gt+"(?="+[Ot,Nt,"$"].join("|")+")",qt+"+"+Zt+"(?="+[Ot,Nt+$t,"$"].join("|")+")",Nt+"?"+$t+"+"+Gt,Nt+"+"+Zt,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",Et,Jt].join("|"),"g"),rn=RegExp("[\\u200d\\ud800-\\udfff"+Bt+It+"]"),en=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,un=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],an=-1,on={};on[T]=on[D]=on[L]=on[U]=on[W]=on[P]=on[N]=on[$]=on[q]=!0,on[y]=on[b]=on[E]=on[m]=on[z]=on[w]=on[x]=on[S]=on[j]=on[A]=on[k]=on[M]=on[C]=on[F]=on[R]=!1;var fn={};fn[y]=fn[b]=fn[E]=fn[z]=fn[m]=fn[w]=fn[T]=fn[D]=fn[L]=fn[U]=fn[W]=fn[j]=fn[A]=fn[k]=fn[M]=fn[C]=fn[F]=fn[O]=fn[P]=fn[N]=fn[$]=fn[q]=!0,fn[x]=fn[S]=fn[R]=!1;var cn={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},ln=parseFloat,sn=parseInt,_n="object"==typeof r.g&&r.g&&r.g.Object===Object&&r.g,hn="object"==typeof self&&self&&self.Object===Object&&self,pn=_n||hn||Function("return this")(),gn=n&&!n.nodeType&&n,vn=gn&&t&&!t.nodeType&&t,dn=vn&&vn.exports===gn,yn=dn&&_n.process,bn=function(){try{var t=vn&&vn.require&&vn.require("util").types;return t||yn&&yn.binding&&yn.binding("util")}catch(t){}}(),mn=bn&&bn.isArrayBuffer,wn=bn&&bn.isDate,xn=bn&&bn.isMap,Sn=bn&&bn.isRegExp,Bn=bn&&bn.isSet,jn=bn&&bn.isTypedArray;function An(t,n,r){switch(r.length){case 0:return t.call(n);case 1:return t.call(n,r[0]);case 2:return t.call(n,r[0],r[1]);case 3:return t.call(n,r[0],r[1],r[2])}return t.apply(n,r)}function kn(t,n,r,e){for(var u=-1,i=null==t?0:t.length;++u-1}function Rn(t,n,r){for(var e=-1,u=null==t?0:t.length;++e-1;);return r}function rr(t,n){for(var r=t.length;r--&&Nn(n,t[r],0)>-1;);return r}function er(t,n){for(var r=t.length,e=0;r--;)t[r]===n&&++e;return e}var ur=Kn({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"}),ir=Kn({"&":"&","<":"<",">":">",'"':""","'":"'"});function ar(t){return"\\"+cn[t]}function or(t){return rn.test(t)}function fr(t){var n=-1,r=Array(t.size);return t.forEach((function(t,e){r[++n]=[e,t]})),r}function cr(t,n){return function(r){return t(n(r))}}function lr(t,n){for(var r=-1,e=t.length,u=0,i=[];++r",""":'"',"'":"'"});var dr=function t(n){var r,e=(n=null==n?pn:dr.defaults(pn.Object(),n,dr.pick(pn,un))).Array,ot=n.Date,Bt=n.Error,jt=n.Function,At=n.Math,kt=n.Object,It=n.RegExp,Mt=n.String,Ct=n.TypeError,Ft=e.prototype,Ot=jt.prototype,Rt=kt.prototype,Et=n["__core-js_shared__"],zt=Ot.toString,Tt=Rt.hasOwnProperty,Dt=0,Lt=(r=/[^.]+$/.exec(Et&&Et.keys&&Et.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"",Ut=Rt.toString,Wt=zt.call(kt),Pt=pn._,Nt=It("^"+zt.call(Tt).replace(ut,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),$t=dn?n.Buffer:u,qt=n.Symbol,Gt=n.Uint8Array,Zt=$t?$t.allocUnsafe:u,Kt=cr(kt.getPrototypeOf,kt),Ht=kt.create,Vt=Rt.propertyIsEnumerable,Jt=Ft.splice,Yt=qt?qt.isConcatSpreadable:u,tn=qt?qt.iterator:u,rn=qt?qt.toStringTag:u,cn=function(){try{var t=hi(kt,"defineProperty");return t({},"",{}),t}catch(t){}}(),_n=n.clearTimeout!==pn.clearTimeout&&n.clearTimeout,hn=ot&&ot.now!==pn.Date.now&&ot.now,gn=n.setTimeout!==pn.setTimeout&&n.setTimeout,vn=At.ceil,yn=At.floor,bn=kt.getOwnPropertySymbols,Un=$t?$t.isBuffer:u,Kn=n.isFinite,yr=Ft.join,br=cr(kt.keys,kt),mr=At.max,wr=At.min,xr=ot.now,Sr=n.parseInt,Br=At.random,jr=Ft.reverse,Ar=hi(n,"DataView"),kr=hi(n,"Map"),Ir=hi(n,"Promise"),Mr=hi(n,"Set"),Cr=hi(n,"WeakMap"),Fr=hi(kt,"create"),Or=Cr&&new Cr,Rr={},Er=Wi(Ar),zr=Wi(kr),Tr=Wi(Ir),Dr=Wi(Mr),Lr=Wi(Cr),Ur=qt?qt.prototype:u,Wr=Ur?Ur.valueOf:u,Pr=Ur?Ur.toString:u;function Nr(t){if(eo(t)&&!Za(t)&&!(t instanceof Zr)){if(t instanceof Gr)return t;if(Tt.call(t,"__wrapped__"))return Pi(t)}return new Gr(t)}var $r=function(){function t(){}return function(n){if(!ro(n))return{};if(Ht)return Ht(n);t.prototype=n;var r=new t;return t.prototype=u,r}}();function qr(){}function Gr(t,n){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!n,this.__index__=0,this.__values__=u}function Zr(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=v,this.__views__=[]}function Kr(t){var n=-1,r=null==t?0:t.length;for(this.clear();++n=n?t:n)),t}function le(t,n,r,e,i,a){var o,f=1&n,c=2&n,l=4&n;if(r&&(o=i?r(t,e,i,a):r(t)),o!==u)return o;if(!ro(t))return t;var s=Za(t);if(s){if(o=function(t){var n=t.length,r=new t.constructor(n);n&&"string"==typeof t[0]&&Tt.call(t,"index")&&(r.index=t.index,r.input=t.input);return r}(t),!f)return Fu(t,o)}else{var _=vi(t),h=_==S||_==B;if(Ja(t))return ju(t,f);if(_==k||_==y||h&&!i){if(o=c||h?{}:yi(t),!f)return c?function(t,n){return Ou(t,gi(t),n)}(t,function(t,n){return t&&Ou(n,zo(n),t)}(o,t)):function(t,n){return Ou(t,pi(t),n)}(t,ae(o,t))}else{if(!fn[_])return i?t:{};o=function(t,n,r){var e=t.constructor;switch(n){case E:return Au(t);case m:case w:return new e(+t);case z:return function(t,n){var r=n?Au(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.byteLength)}(t,r);case T:case D:case L:case U:case W:case P:case N:case $:case q:return ku(t,r);case j:return new e;case A:case F:return new e(t);case M:return function(t){var n=new t.constructor(t.source,gt.exec(t));return n.lastIndex=t.lastIndex,n}(t);case C:return new e;case O:return u=t,Wr?kt(Wr.call(u)):{}}var u}(t,_,f)}}a||(a=new Yr);var p=a.get(t);if(p)return p;a.set(t,o),fo(t)?t.forEach((function(e){o.add(le(e,n,r,e,t,a))})):uo(t)&&t.forEach((function(e,u){o.set(u,le(e,n,r,u,t,a))}));var g=s?u:(l?c?ai:ii:c?zo:Eo)(t);return In(g||t,(function(e,u){g&&(e=t[u=e]),ee(o,u,le(e,n,r,u,t,a))})),o}function se(t,n,r){var e=r.length;if(null==t)return!e;for(t=kt(t);e--;){var i=r[e],a=n[i],o=t[i];if(o===u&&!(i in t)||!a(o))return!1}return!0}function _e(t,n,r){if("function"!=typeof t)throw new Ct(i);return Ri((function(){t.apply(u,r)}),n)}function he(t,n,r,e){var u=-1,i=On,a=!0,o=t.length,f=[],c=n.length;if(!o)return f;r&&(n=En(n,Qn(r))),e?(i=Rn,a=!1):n.length>=200&&(i=tr,a=!1,n=new Jr(n));t:for(;++u-1},Hr.prototype.set=function(t,n){var r=this.__data__,e=ue(r,t);return e<0?(++this.size,r.push([t,n])):r[e][1]=n,this},Vr.prototype.clear=function(){this.size=0,this.__data__={hash:new Kr,map:new(kr||Hr),string:new Kr}},Vr.prototype.delete=function(t){var n=si(this,t).delete(t);return this.size-=n?1:0,n},Vr.prototype.get=function(t){return si(this,t).get(t)},Vr.prototype.has=function(t){return si(this,t).has(t)},Vr.prototype.set=function(t,n){var r=si(this,t),e=r.size;return r.set(t,n),this.size+=r.size==e?0:1,this},Jr.prototype.add=Jr.prototype.push=function(t){return this.__data__.set(t,a),this},Jr.prototype.has=function(t){return this.__data__.has(t)},Yr.prototype.clear=function(){this.__data__=new Hr,this.size=0},Yr.prototype.delete=function(t){var n=this.__data__,r=n.delete(t);return this.size=n.size,r},Yr.prototype.get=function(t){return this.__data__.get(t)},Yr.prototype.has=function(t){return this.__data__.has(t)},Yr.prototype.set=function(t,n){var r=this.__data__;if(r instanceof Hr){var e=r.__data__;if(!kr||e.length<199)return e.push([t,n]),this.size=++r.size,this;r=this.__data__=new Vr(e)}return r.set(t,n),this.size=r.size,this};var pe=zu(xe),ge=zu(Se,!0);function ve(t,n){var r=!0;return pe(t,(function(t,e,u){return r=!!n(t,e,u)})),r}function de(t,n,r){for(var e=-1,i=t.length;++e0&&r(o)?n>1?be(o,n-1,r,e,u):zn(u,o):e||(u[u.length]=o)}return u}var me=Tu(),we=Tu(!0);function xe(t,n){return t&&me(t,n,Eo)}function Se(t,n){return t&&we(t,n,Eo)}function Be(t,n){return Fn(n,(function(n){return Xa(t[n])}))}function je(t,n){for(var r=0,e=(n=wu(n,t)).length;null!=t&&rn}function Me(t,n){return null!=t&&Tt.call(t,n)}function Ce(t,n){return null!=t&&n in kt(t)}function Fe(t,n,r){for(var i=r?Rn:On,a=t[0].length,o=t.length,f=o,c=e(o),l=1/0,s=[];f--;){var _=t[f];f&&n&&(_=En(_,Qn(n))),l=wr(_.length,l),c[f]=!r&&(n||a>=120&&_.length>=120)?new Jr(f&&_):u}_=t[0];var h=-1,p=c[0];t:for(;++h=o?f:f*("desc"==r[e]?-1:1)}return t.index-n.index}(t,n,r)}))}function Ke(t,n,r){for(var e=-1,u=n.length,i={};++e-1;)o!==t&&Jt.call(o,f,1),Jt.call(t,f,1);return t}function Ve(t,n){for(var r=t?n.length:0,e=r-1;r--;){var u=n[r];if(r==e||u!==i){var i=u;mi(u)?Jt.call(t,u,1):hu(t,u)}}return t}function Je(t,n){return t+yn(Br()*(n-t+1))}function Ye(t,n){var r="";if(!t||n<1||n>p)return r;do{n%2&&(r+=t),(n=yn(n/2))&&(t+=t)}while(n);return r}function Qe(t,n){return Ei(Ii(t,n,of),t+"")}function Xe(t){return Xr($o(t))}function tu(t,n){var r=$o(t);return Di(r,ce(n,0,r.length))}function nu(t,n,r,e){if(!ro(t))return t;for(var i=-1,a=(n=wu(n,t)).length,o=a-1,f=t;null!=f&&++ii?0:i+n),(r=r>i?i:r)<0&&(r+=i),i=n>r?0:r-n>>>0,n>>>=0;for(var a=e(i);++u>>1,a=t[i];null!==a&&!lo(a)&&(r?a<=n:a=200){var c=n?null:Yu(t);if(c)return sr(c);a=!1,u=tr,f=new Jr}else f=n?[]:o;t:for(;++e=e?t:iu(t,n,r)}var Bu=_n||function(t){return pn.clearTimeout(t)};function ju(t,n){if(n)return t.slice();var r=t.length,e=Zt?Zt(r):new t.constructor(r);return t.copy(e),e}function Au(t){var n=new t.constructor(t.byteLength);return new Gt(n).set(new Gt(t)),n}function ku(t,n){var r=n?Au(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.length)}function Iu(t,n){if(t!==n){var r=t!==u,e=null===t,i=t==t,a=lo(t),o=n!==u,f=null===n,c=n==n,l=lo(n);if(!f&&!l&&!a&&t>n||a&&o&&c&&!f&&!l||e&&o&&c||!r&&c||!i)return 1;if(!e&&!a&&!l&&t1?r[i-1]:u,o=i>2?r[2]:u;for(a=t.length>3&&"function"==typeof a?(i--,a):u,o&&wi(r[0],r[1],o)&&(a=i<3?u:a,i=1),n=kt(n);++e-1?i[a?n[o]:o]:u}}function Pu(t){return ui((function(n){var r=n.length,e=r,a=Gr.prototype.thru;for(t&&n.reverse();e--;){var o=n[e];if("function"!=typeof o)throw new Ct(i);if(a&&!f&&"wrapper"==fi(o))var f=new Gr([],!0)}for(e=f?e:r;++e1&&b.reverse(),h&&lf))return!1;var l=a.get(t),s=a.get(n);if(l&&s)return l==n&&s==t;var _=-1,h=!0,p=2&r?new Jr:u;for(a.set(t,n),a.set(n,t);++_-1&&t%1==0&&t1?"& ":"")+n[e],n=n.join(r>2?", ":" "),t.replace(ft,"{\n/* [wrapped with "+n+"] */\n")}(e,function(t,n){return In(d,(function(r){var e="_."+r[0];n&r[1]&&!On(t,e)&&t.push(e)})),t.sort()}(function(t){var n=t.match(ct);return n?n[1].split(lt):[]}(e),r)))}function Ti(t){var n=0,r=0;return function(){var e=xr(),i=16-(e-r);if(r=e,i>0){if(++n>=800)return arguments[0]}else n=0;return t.apply(u,arguments)}}function Di(t,n){var r=-1,e=t.length,i=e-1;for(n=n===u?e:n;++r1?t[n-1]:u;return r="function"==typeof r?(t.pop(),r):u,oa(t,r)}));function pa(t){var n=Nr(t);return n.__chain__=!0,n}function ga(t,n){return n(t)}var va=ui((function(t){var n=t.length,r=n?t[0]:0,e=this.__wrapped__,i=function(n){return fe(n,t)};return!(n>1||this.__actions__.length)&&e instanceof Zr&&mi(r)?((e=e.slice(r,+r+(n?1:0))).__actions__.push({func:ga,args:[i],thisArg:u}),new Gr(e,this.__chain__).thru((function(t){return n&&!t.length&&t.push(u),t}))):this.thru(i)}));var da=Ru((function(t,n,r){Tt.call(t,r)?++t[r]:oe(t,r,1)}));var ya=Wu(Gi),ba=Wu(Zi);function ma(t,n){return(Za(t)?In:pe)(t,li(n,3))}function wa(t,n){return(Za(t)?Mn:ge)(t,li(n,3))}var xa=Ru((function(t,n,r){Tt.call(t,r)?t[r].push(n):oe(t,r,[n])}));var Sa=Qe((function(t,n,r){var u=-1,i="function"==typeof n,a=Ha(t)?e(t.length):[];return pe(t,(function(t){a[++u]=i?An(n,t,r):Oe(t,n,r)})),a})),Ba=Ru((function(t,n,r){oe(t,r,n)}));function ja(t,n){return(Za(t)?En:Pe)(t,li(n,3))}var Aa=Ru((function(t,n,r){t[r?0:1].push(n)}),(function(){return[[],[]]}));var ka=Qe((function(t,n){if(null==t)return[];var r=n.length;return r>1&&wi(t,n[0],n[1])?n=[]:r>2&&wi(n[0],n[1],n[2])&&(n=[n[0]]),Ze(t,be(n,1),[])})),Ia=hn||function(){return pn.Date.now()};function Ma(t,n,r){return n=r?u:n,n=t&&null==n?t.length:n,Xu(t,s,u,u,u,u,n)}function Ca(t,n){var r;if("function"!=typeof n)throw new Ct(i);return t=vo(t),function(){return--t>0&&(r=n.apply(this,arguments)),t<=1&&(n=u),r}}var Fa=Qe((function(t,n,r){var e=1;if(r.length){var u=lr(r,ci(Fa));e|=c}return Xu(t,e,n,r,u)})),Oa=Qe((function(t,n,r){var e=3;if(r.length){var u=lr(r,ci(Oa));e|=c}return Xu(n,e,t,r,u)}));function Ra(t,n,r){var e,a,o,f,c,l,s=0,_=!1,h=!1,p=!0;if("function"!=typeof t)throw new Ct(i);function g(n){var r=e,i=a;return e=a=u,s=n,f=t.apply(i,r)}function v(t){return s=t,c=Ri(y,n),_?g(t):f}function d(t){var r=t-l;return l===u||r>=n||r<0||h&&t-s>=o}function y(){var t=Ia();if(d(t))return b(t);c=Ri(y,function(t){var r=n-(t-l);return h?wr(r,o-(t-s)):r}(t))}function b(t){return c=u,p&&e?g(t):(e=a=u,f)}function m(){var t=Ia(),r=d(t);if(e=arguments,a=this,l=t,r){if(c===u)return v(l);if(h)return Bu(c),c=Ri(y,n),g(l)}return c===u&&(c=Ri(y,n)),f}return n=bo(n)||0,ro(r)&&(_=!!r.leading,o=(h="maxWait"in r)?mr(bo(r.maxWait)||0,n):o,p="trailing"in r?!!r.trailing:p),m.cancel=function(){c!==u&&Bu(c),s=0,e=l=a=c=u},m.flush=function(){return c===u?f:b(Ia())},m}var Ea=Qe((function(t,n){return _e(t,1,n)})),za=Qe((function(t,n,r){return _e(t,bo(n)||0,r)}));function Ta(t,n){if("function"!=typeof t||null!=n&&"function"!=typeof n)throw new Ct(i);var r=function(){var e=arguments,u=n?n.apply(this,e):e[0],i=r.cache;if(i.has(u))return i.get(u);var a=t.apply(this,e);return r.cache=i.set(u,a)||i,a};return r.cache=new(Ta.Cache||Vr),r}function Da(t){if("function"!=typeof t)throw new Ct(i);return function(){var n=arguments;switch(n.length){case 0:return!t.call(this);case 1:return!t.call(this,n[0]);case 2:return!t.call(this,n[0],n[1]);case 3:return!t.call(this,n[0],n[1],n[2])}return!t.apply(this,n)}}Ta.Cache=Vr;var La=xu((function(t,n){var r=(n=1==n.length&&Za(n[0])?En(n[0],Qn(li())):En(be(n,1),Qn(li()))).length;return Qe((function(e){for(var u=-1,i=wr(e.length,r);++u=n})),Ga=Re(function(){return arguments}())?Re:function(t){return eo(t)&&Tt.call(t,"callee")&&!Vt.call(t,"callee")},Za=e.isArray,Ka=mn?Qn(mn):function(t){return eo(t)&&ke(t)==E};function Ha(t){return null!=t&&no(t.length)&&!Xa(t)}function Va(t){return eo(t)&&Ha(t)}var Ja=Un||mf,Ya=wn?Qn(wn):function(t){return eo(t)&&ke(t)==w};function Qa(t){if(!eo(t))return!1;var n=ke(t);return n==x||"[object DOMException]"==n||"string"==typeof t.message&&"string"==typeof t.name&&!ao(t)}function Xa(t){if(!ro(t))return!1;var n=ke(t);return n==S||n==B||"[object AsyncFunction]"==n||"[object Proxy]"==n}function to(t){return"number"==typeof t&&t==vo(t)}function no(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=p}function ro(t){var n=typeof t;return null!=t&&("object"==n||"function"==n)}function eo(t){return null!=t&&"object"==typeof t}var uo=xn?Qn(xn):function(t){return eo(t)&&vi(t)==j};function io(t){return"number"==typeof t||eo(t)&&ke(t)==A}function ao(t){if(!eo(t)||ke(t)!=k)return!1;var n=Kt(t);if(null===n)return!0;var r=Tt.call(n,"constructor")&&n.constructor;return"function"==typeof r&&r instanceof r&&zt.call(r)==Wt}var oo=Sn?Qn(Sn):function(t){return eo(t)&&ke(t)==M};var fo=Bn?Qn(Bn):function(t){return eo(t)&&vi(t)==C};function co(t){return"string"==typeof t||!Za(t)&&eo(t)&&ke(t)==F}function lo(t){return"symbol"==typeof t||eo(t)&&ke(t)==O}var so=jn?Qn(jn):function(t){return eo(t)&&no(t.length)&&!!on[ke(t)]};var _o=Hu(We),ho=Hu((function(t,n){return t<=n}));function po(t){if(!t)return[];if(Ha(t))return co(t)?pr(t):Fu(t);if(tn&&t[tn])return function(t){for(var n,r=[];!(n=t.next()).done;)r.push(n.value);return r}(t[tn]());var n=vi(t);return(n==j?fr:n==C?sr:$o)(t)}function go(t){return t?(t=bo(t))===h||t===-1/0?17976931348623157e292*(t<0?-1:1):t==t?t:0:0===t?t:0}function vo(t){var n=go(t),r=n%1;return n==n?r?n-r:n:0}function yo(t){return t?ce(vo(t),0,v):0}function bo(t){if("number"==typeof t)return t;if(lo(t))return g;if(ro(t)){var n="function"==typeof t.valueOf?t.valueOf():t;t=ro(n)?n+"":n}if("string"!=typeof t)return 0===t?t:+t;t=Yn(t);var r=dt.test(t);return r||bt.test(t)?sn(t.slice(2),r?2:8):vt.test(t)?g:+t}function mo(t){return Ou(t,zo(t))}function wo(t){return null==t?"":su(t)}var xo=Eu((function(t,n){if(ji(n)||Ha(n))Ou(n,Eo(n),t);else for(var r in n)Tt.call(n,r)&&ee(t,r,n[r])})),So=Eu((function(t,n){Ou(n,zo(n),t)})),Bo=Eu((function(t,n,r,e){Ou(n,zo(n),t,e)})),jo=Eu((function(t,n,r,e){Ou(n,Eo(n),t,e)})),Ao=ui(fe);var ko=Qe((function(t,n){t=kt(t);var r=-1,e=n.length,i=e>2?n[2]:u;for(i&&wi(n[0],n[1],i)&&(e=1);++r1),n})),Ou(t,ai(t),r),e&&(r=le(r,7,ri));for(var u=n.length;u--;)hu(r,n[u]);return r}));var Uo=ui((function(t,n){return null==t?{}:function(t,n){return Ke(t,n,(function(n,r){return Co(t,r)}))}(t,n)}));function Wo(t,n){if(null==t)return{};var r=En(ai(t),(function(t){return[t]}));return n=li(n),Ke(t,r,(function(t,r){return n(t,r[0])}))}var Po=Qu(Eo),No=Qu(zo);function $o(t){return null==t?[]:Xn(t,Eo(t))}var qo=Lu((function(t,n,r){return n=n.toLowerCase(),t+(r?Go(n):n)}));function Go(t){return Xo(wo(t).toLowerCase())}function Zo(t){return(t=wo(t))&&t.replace(wt,ur).replace(Xt,"")}var Ko=Lu((function(t,n,r){return t+(r?"-":"")+n.toLowerCase()})),Ho=Lu((function(t,n,r){return t+(r?" ":"")+n.toLowerCase()})),Vo=Du("toLowerCase");var Jo=Lu((function(t,n,r){return t+(r?"_":"")+n.toLowerCase()}));var Yo=Lu((function(t,n,r){return t+(r?" ":"")+Xo(n)}));var Qo=Lu((function(t,n,r){return t+(r?" ":"")+n.toUpperCase()})),Xo=Du("toUpperCase");function tf(t,n,r){return t=wo(t),(n=r?u:n)===u?function(t){return en.test(t)}(t)?function(t){return t.match(nn)||[]}(t):function(t){return t.match(st)||[]}(t):t.match(n)||[]}var nf=Qe((function(t,n){try{return An(t,u,n)}catch(t){return Qa(t)?t:new Bt(t)}})),rf=ui((function(t,n){return In(n,(function(n){n=Ui(n),oe(t,n,Fa(t[n],t))})),t}));function ef(t){return function(){return t}}var uf=Pu(),af=Pu(!0);function of(t){return t}function ff(t){return De("function"==typeof t?t:le(t,1))}var cf=Qe((function(t,n){return function(r){return Oe(r,t,n)}})),lf=Qe((function(t,n){return function(r){return Oe(t,r,n)}}));function sf(t,n,r){var e=Eo(n),u=Be(n,e);null!=r||ro(n)&&(u.length||!e.length)||(r=n,n=t,t=this,u=Be(n,Eo(n)));var i=!(ro(r)&&"chain"in r&&!r.chain),a=Xa(t);return In(u,(function(r){var e=n[r];t[r]=e,a&&(t.prototype[r]=function(){var n=this.__chain__;if(i||n){var r=t(this.__wrapped__),u=r.__actions__=Fu(this.__actions__);return u.push({func:e,args:arguments,thisArg:t}),r.__chain__=n,r}return e.apply(t,zn([this.value()],arguments))})})),t}function _f(){}var hf=Gu(En),pf=Gu(Cn),gf=Gu(Ln);function vf(t){return xi(t)?Zn(Ui(t)):function(t){return function(n){return je(n,t)}}(t)}var df=Ku(),yf=Ku(!0);function bf(){return[]}function mf(){return!1}var wf=qu((function(t,n){return t+n}),0),xf=Ju("ceil"),Sf=qu((function(t,n){return t/n}),1),Bf=Ju("floor");var jf,Af=qu((function(t,n){return t*n}),1),kf=Ju("round"),If=qu((function(t,n){return t-n}),0);return Nr.after=function(t,n){if("function"!=typeof n)throw new Ct(i);return t=vo(t),function(){if(--t<1)return n.apply(this,arguments)}},Nr.ary=Ma,Nr.assign=xo,Nr.assignIn=So,Nr.assignInWith=Bo,Nr.assignWith=jo,Nr.at=Ao,Nr.before=Ca,Nr.bind=Fa,Nr.bindAll=rf,Nr.bindKey=Oa,Nr.castArray=function(){if(!arguments.length)return[];var t=arguments[0];return Za(t)?t:[t]},Nr.chain=pa,Nr.chunk=function(t,n,r){n=(r?wi(t,n,r):n===u)?1:mr(vo(n),0);var i=null==t?0:t.length;if(!i||n<1)return[];for(var a=0,o=0,f=e(vn(i/n));ai?0:i+r),(e=e===u||e>i?i:vo(e))<0&&(e+=i),e=r>e?0:yo(e);r>>0)?(t=wo(t))&&("string"==typeof n||null!=n&&!oo(n))&&!(n=su(n))&&or(t)?Su(pr(t),0,r):t.split(n,r):[]},Nr.spread=function(t,n){if("function"!=typeof t)throw new Ct(i);return n=null==n?0:mr(vo(n),0),Qe((function(r){var e=r[n],u=Su(r,0,n);return e&&zn(u,e),An(t,this,u)}))},Nr.tail=function(t){var n=null==t?0:t.length;return n?iu(t,1,n):[]},Nr.take=function(t,n,r){return t&&t.length?iu(t,0,(n=r||n===u?1:vo(n))<0?0:n):[]},Nr.takeRight=function(t,n,r){var e=null==t?0:t.length;return e?iu(t,(n=e-(n=r||n===u?1:vo(n)))<0?0:n,e):[]},Nr.takeRightWhile=function(t,n){return t&&t.length?gu(t,li(n,3),!1,!0):[]},Nr.takeWhile=function(t,n){return t&&t.length?gu(t,li(n,3)):[]},Nr.tap=function(t,n){return n(t),t},Nr.throttle=function(t,n,r){var e=!0,u=!0;if("function"!=typeof t)throw new Ct(i);return ro(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),Ra(t,n,{leading:e,maxWait:n,trailing:u})},Nr.thru=ga,Nr.toArray=po,Nr.toPairs=Po,Nr.toPairsIn=No,Nr.toPath=function(t){return Za(t)?En(t,Ui):lo(t)?[t]:Fu(Li(wo(t)))},Nr.toPlainObject=mo,Nr.transform=function(t,n,r){var e=Za(t),u=e||Ja(t)||so(t);if(n=li(n,4),null==r){var i=t&&t.constructor;r=u?e?new i:[]:ro(t)&&Xa(i)?$r(Kt(t)):{}}return(u?In:xe)(t,(function(t,e,u){return n(r,t,e,u)})),r},Nr.unary=function(t){return Ma(t,1)},Nr.union=ea,Nr.unionBy=ua,Nr.unionWith=ia,Nr.uniq=function(t){return t&&t.length?_u(t):[]},Nr.uniqBy=function(t,n){return t&&t.length?_u(t,li(n,2)):[]},Nr.uniqWith=function(t,n){return n="function"==typeof n?n:u,t&&t.length?_u(t,u,n):[]},Nr.unset=function(t,n){return null==t||hu(t,n)},Nr.unzip=aa,Nr.unzipWith=oa,Nr.update=function(t,n,r){return null==t?t:pu(t,n,mu(r))},Nr.updateWith=function(t,n,r,e){return e="function"==typeof e?e:u,null==t?t:pu(t,n,mu(r),e)},Nr.values=$o,Nr.valuesIn=function(t){return null==t?[]:Xn(t,zo(t))},Nr.without=fa,Nr.words=tf,Nr.wrap=function(t,n){return Ua(mu(n),t)},Nr.xor=ca,Nr.xorBy=la,Nr.xorWith=sa,Nr.zip=_a,Nr.zipObject=function(t,n){return yu(t||[],n||[],ee)},Nr.zipObjectDeep=function(t,n){return yu(t||[],n||[],nu)},Nr.zipWith=ha,Nr.entries=Po,Nr.entriesIn=No,Nr.extend=So,Nr.extendWith=Bo,sf(Nr,Nr),Nr.add=wf,Nr.attempt=nf,Nr.camelCase=qo,Nr.capitalize=Go,Nr.ceil=xf,Nr.clamp=function(t,n,r){return r===u&&(r=n,n=u),r!==u&&(r=(r=bo(r))==r?r:0),n!==u&&(n=(n=bo(n))==n?n:0),ce(bo(t),n,r)},Nr.clone=function(t){return le(t,4)},Nr.cloneDeep=function(t){return le(t,5)},Nr.cloneDeepWith=function(t,n){return le(t,5,n="function"==typeof n?n:u)},Nr.cloneWith=function(t,n){return le(t,4,n="function"==typeof n?n:u)},Nr.conformsTo=function(t,n){return null==n||se(t,n,Eo(n))},Nr.deburr=Zo,Nr.defaultTo=function(t,n){return null==t||t!=t?n:t},Nr.divide=Sf,Nr.endsWith=function(t,n,r){t=wo(t),n=su(n);var e=t.length,i=r=r===u?e:ce(vo(r),0,e);return(r-=n.length)>=0&&t.slice(r,i)==n},Nr.eq=Na,Nr.escape=function(t){return(t=wo(t))&&Y.test(t)?t.replace(V,ir):t},Nr.escapeRegExp=function(t){return(t=wo(t))&&it.test(t)?t.replace(ut,"\\$&"):t},Nr.every=function(t,n,r){var e=Za(t)?Cn:ve;return r&&wi(t,n,r)&&(n=u),e(t,li(n,3))},Nr.find=ya,Nr.findIndex=Gi,Nr.findKey=function(t,n){return Wn(t,li(n,3),xe)},Nr.findLast=ba,Nr.findLastIndex=Zi,Nr.findLastKey=function(t,n){return Wn(t,li(n,3),Se)},Nr.floor=Bf,Nr.forEach=ma,Nr.forEachRight=wa,Nr.forIn=function(t,n){return null==t?t:me(t,li(n,3),zo)},Nr.forInRight=function(t,n){return null==t?t:we(t,li(n,3),zo)},Nr.forOwn=function(t,n){return t&&xe(t,li(n,3))},Nr.forOwnRight=function(t,n){return t&&Se(t,li(n,3))},Nr.get=Mo,Nr.gt=$a,Nr.gte=qa,Nr.has=function(t,n){return null!=t&&di(t,n,Me)},Nr.hasIn=Co,Nr.head=Hi,Nr.identity=of,Nr.includes=function(t,n,r,e){t=Ha(t)?t:$o(t),r=r&&!e?vo(r):0;var u=t.length;return r<0&&(r=mr(u+r,0)),co(t)?r<=u&&t.indexOf(n,r)>-1:!!u&&Nn(t,n,r)>-1},Nr.indexOf=function(t,n,r){var e=null==t?0:t.length;if(!e)return-1;var u=null==r?0:vo(r);return u<0&&(u=mr(e+u,0)),Nn(t,n,u)},Nr.inRange=function(t,n,r){return n=go(n),r===u?(r=n,n=0):r=go(r),function(t,n,r){return t>=wr(n,r)&&t=-9007199254740991&&t<=p},Nr.isSet=fo,Nr.isString=co,Nr.isSymbol=lo,Nr.isTypedArray=so,Nr.isUndefined=function(t){return t===u},Nr.isWeakMap=function(t){return eo(t)&&vi(t)==R},Nr.isWeakSet=function(t){return eo(t)&&"[object WeakSet]"==ke(t)},Nr.join=function(t,n){return null==t?"":yr.call(t,n)},Nr.kebabCase=Ko,Nr.last=Qi,Nr.lastIndexOf=function(t,n,r){var e=null==t?0:t.length;if(!e)return-1;var i=e;return r!==u&&(i=(i=vo(r))<0?mr(e+i,0):wr(i,e-1)),n==n?function(t,n,r){for(var e=r+1;e--;)if(t[e]===n)return e;return e}(t,n,i):Pn(t,qn,i,!0)},Nr.lowerCase=Ho,Nr.lowerFirst=Vo,Nr.lt=_o,Nr.lte=ho,Nr.max=function(t){return t&&t.length?de(t,of,Ie):u},Nr.maxBy=function(t,n){return t&&t.length?de(t,li(n,2),Ie):u},Nr.mean=function(t){return Gn(t,of)},Nr.meanBy=function(t,n){return Gn(t,li(n,2))},Nr.min=function(t){return t&&t.length?de(t,of,We):u},Nr.minBy=function(t,n){return t&&t.length?de(t,li(n,2),We):u},Nr.stubArray=bf,Nr.stubFalse=mf,Nr.stubObject=function(){return{}},Nr.stubString=function(){return""},Nr.stubTrue=function(){return!0},Nr.multiply=Af,Nr.nth=function(t,n){return t&&t.length?Ge(t,vo(n)):u},Nr.noConflict=function(){return pn._===this&&(pn._=Pt),this},Nr.noop=_f,Nr.now=Ia,Nr.pad=function(t,n,r){t=wo(t);var e=(n=vo(n))?hr(t):0;if(!n||e>=n)return t;var u=(n-e)/2;return Zu(yn(u),r)+t+Zu(vn(u),r)},Nr.padEnd=function(t,n,r){t=wo(t);var e=(n=vo(n))?hr(t):0;return n&&en){var e=t;t=n,n=e}if(r||t%1||n%1){var i=Br();return wr(t+i*(n-t+ln("1e-"+((i+"").length-1))),n)}return Je(t,n)},Nr.reduce=function(t,n,r){var e=Za(t)?Tn:Hn,u=arguments.length<3;return e(t,li(n,4),r,u,pe)},Nr.reduceRight=function(t,n,r){var e=Za(t)?Dn:Hn,u=arguments.length<3;return e(t,li(n,4),r,u,ge)},Nr.repeat=function(t,n,r){return n=(r?wi(t,n,r):n===u)?1:vo(n),Ye(wo(t),n)},Nr.replace=function(){var t=arguments,n=wo(t[0]);return t.length<3?n:n.replace(t[1],t[2])},Nr.result=function(t,n,r){var e=-1,i=(n=wu(n,t)).length;for(i||(i=1,t=u);++ep)return[];var r=v,e=wr(t,v);n=li(n),t-=v;for(var u=Jn(e,n);++r=a)return t;var f=r-hr(e);if(f<1)return e;var c=o?Su(o,0,f).join(""):t.slice(0,f);if(i===u)return c+e;if(o&&(f+=c.length-f),oo(i)){if(t.slice(f).search(i)){var l,s=c;for(i.global||(i=It(i.source,wo(gt.exec(i))+"g")),i.lastIndex=0;l=i.exec(s);)var _=l.index;c=c.slice(0,_===u?f:_)}}else if(t.indexOf(su(i),f)!=f){var h=c.lastIndexOf(i);h>-1&&(c=c.slice(0,h))}return c+e},Nr.unescape=function(t){return(t=wo(t))&&J.test(t)?t.replace(H,vr):t},Nr.uniqueId=function(t){var n=++Dt;return wo(t)+n},Nr.upperCase=Qo,Nr.upperFirst=Xo,Nr.each=ma,Nr.eachRight=wa,Nr.first=Hi,sf(Nr,(jf={},xe(Nr,(function(t,n){Tt.call(Nr.prototype,n)||(jf[n]=t)})),jf),{chain:!1}),Nr.VERSION="4.17.21",In(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(t){Nr[t].placeholder=Nr})),In(["drop","take"],(function(t,n){Zr.prototype[t]=function(r){r=r===u?1:mr(vo(r),0);var e=this.__filtered__&&!n?new Zr(this):this.clone();return e.__filtered__?e.__takeCount__=wr(r,e.__takeCount__):e.__views__.push({size:wr(r,v),type:t+(e.__dir__<0?"Right":"")}),e},Zr.prototype[t+"Right"]=function(n){return this.reverse()[t](n).reverse()}})),In(["filter","map","takeWhile"],(function(t,n){var r=n+1,e=1==r||3==r;Zr.prototype[t]=function(t){var n=this.clone();return n.__iteratees__.push({iteratee:li(t,3),type:r}),n.__filtered__=n.__filtered__||e,n}})),In(["head","last"],(function(t,n){var r="take"+(n?"Right":"");Zr.prototype[t]=function(){return this[r](1).value()[0]}})),In(["initial","tail"],(function(t,n){var r="drop"+(n?"":"Right");Zr.prototype[t]=function(){return this.__filtered__?new Zr(this):this[r](1)}})),Zr.prototype.compact=function(){return this.filter(of)},Zr.prototype.find=function(t){return this.filter(t).head()},Zr.prototype.findLast=function(t){return this.reverse().find(t)},Zr.prototype.invokeMap=Qe((function(t,n){return"function"==typeof t?new Zr(this):this.map((function(r){return Oe(r,t,n)}))})),Zr.prototype.reject=function(t){return this.filter(Da(li(t)))},Zr.prototype.slice=function(t,n){t=vo(t);var r=this;return r.__filtered__&&(t>0||n<0)?new Zr(r):(t<0?r=r.takeRight(-t):t&&(r=r.drop(t)),n!==u&&(r=(n=vo(n))<0?r.dropRight(-n):r.take(n-t)),r)},Zr.prototype.takeRightWhile=function(t){return this.reverse().takeWhile(t).reverse()},Zr.prototype.toArray=function(){return this.take(v)},xe(Zr.prototype,(function(t,n){var r=/^(?:filter|find|map|reject)|While$/.test(n),e=/^(?:head|last)$/.test(n),i=Nr[e?"take"+("last"==n?"Right":""):n],a=e||/^find/.test(n);i&&(Nr.prototype[n]=function(){var n=this.__wrapped__,o=e?[1]:arguments,f=n instanceof Zr,c=o[0],l=f||Za(n),s=function(t){var n=i.apply(Nr,zn([t],o));return e&&_?n[0]:n};l&&r&&"function"==typeof c&&1!=c.length&&(f=l=!1);var _=this.__chain__,h=!!this.__actions__.length,p=a&&!_,g=f&&!h;if(!a&&l){n=g?n:new Zr(this);var v=t.apply(n,o);return v.__actions__.push({func:ga,args:[s],thisArg:u}),new Gr(v,_)}return p&&g?t.apply(this,o):(v=this.thru(s),p?e?v.value()[0]:v.value():v)})})),In(["pop","push","shift","sort","splice","unshift"],(function(t){var n=Ft[t],r=/^(?:push|sort|unshift)$/.test(t)?"tap":"thru",e=/^(?:pop|shift)$/.test(t);Nr.prototype[t]=function(){var t=arguments;if(e&&!this.__chain__){var u=this.value();return n.apply(Za(u)?u:[],t)}return this[r]((function(r){return n.apply(Za(r)?r:[],t)}))}})),xe(Zr.prototype,(function(t,n){var r=Nr[n];if(r){var e=r.name+"";Tt.call(Rr,e)||(Rr[e]=[]),Rr[e].push({name:n,func:r})}})),Rr[Nu(u,2).name]=[{name:"wrapper",func:u}],Zr.prototype.clone=function(){var t=new Zr(this.__wrapped__);return t.__actions__=Fu(this.__actions__),t.__dir__=this.__dir__,t.__filtered__=this.__filtered__,t.__iteratees__=Fu(this.__iteratees__),t.__takeCount__=this.__takeCount__,t.__views__=Fu(this.__views__),t},Zr.prototype.reverse=function(){if(this.__filtered__){var t=new Zr(this);t.__dir__=-1,t.__filtered__=!0}else(t=this.clone()).__dir__*=-1;return t},Zr.prototype.value=function(){var t=this.__wrapped__.value(),n=this.__dir__,r=Za(t),e=n<0,u=r?t.length:0,i=function(t,n,r){var e=-1,u=r.length;for(;++e=this.__values__.length;return{done:t,value:t?u:this.__values__[this.__index__++]}},Nr.prototype.plant=function(t){for(var n,r=this;r instanceof qr;){var e=Pi(r);e.__index__=0,e.__values__=u,n?i.__wrapped__=e:n=e;var i=e;r=r.__wrapped__}return i.__wrapped__=t,n},Nr.prototype.reverse=function(){var t=this.__wrapped__;if(t instanceof Zr){var n=t;return this.__actions__.length&&(n=new Zr(this)),(n=n.reverse()).__actions__.push({func:ga,args:[ra],thisArg:u}),new Gr(n,this.__chain__)}return this.thru(ra)},Nr.prototype.toJSON=Nr.prototype.valueOf=Nr.prototype.value=function(){return vu(this.__wrapped__,this.__actions__)},Nr.prototype.first=Nr.prototype.head,tn&&(Nr.prototype[tn]=function(){return this}),Nr}();pn._=dr,(e=function(){return dr}.call(n,r,n,t))===u||(t.exports=e)}.call(this)}},n={};function r(e){var u=n[e];if(void 0!==u)return u.exports;var i=n[e]={id:e,loaded:!1,exports:{}};return t[e].call(i.exports,i,i.exports,r),i.loaded=!0,i.exports}r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),r.nmd=t=>(t.paths=[],t.children||(t.children=[]),t);var e=r(6014);freeaps_profile=e})(); +var freeaps_profile;(()=>{var t={902:t=>{"use strict";function n(t,n){t.length>0&&(t+="\n");for(var r=n.length,e=0;e{"use strict";t.exports=function(t){var n=new Date;return n.setHours("00"),n.setMinutes("00"),n.setSeconds("00"),n.getTime()+60*t*1e3}},118:(t,n,r)=>{"use strict";var e=r(125);n.maxDailyBasal=function(t){var n=e.maxBy(t.basals,(function(t){return Number(t.rate)}));return 1e3*Number(n.rate)/1e3},n.maxBasalLookup=function(t){return t.settings.maxBasal},n.basalLookup=function(t,n){var r=n;void 0===n&&(r=new Date);var u=e.sortBy(t,(function(t){return t.i})),i=u[u.length-1].rate;if(0!==i){for(var o=60*r.getHours()+r.getMinutes(),a=0;a=u[a].minutes&&o{"use strict";var e=r(53),u=r(902).console_error;function i(t,n,r){var i=new Date,o=n.carbratio;if(void 0===o||void 0===o.schedule);else{var a;if("grams"===o.units||"exchanges"===o.units){a=o.schedule[o.schedule.length-1];for(var f=0;f=e(o.schedule[f].offset)&&i150)return void u(t,"Error: carbRatio of "+a+" out of bounds.");break}return"exchanges"===o.units&&(a.ratio=12/a.ratio),a.ratio}u(t,"Error: Unsupported carb_ratio units "+o.units)}}i.carbRatioLookup=i,t.exports=i},825:(t,n,r)=>{"use strict";var e=r(118),u=r(743),i=r(335),o=r(664),a=r(125),f=r(902),c=f.console_error;f.console_log;function l(){return{max_iob:0,max_daily_safety_multiplier:3,current_basal_safety_multiplier:4,autosens_max:1.2,autosens_min:.7,rewind_resets_autosens:!0,high_temptarget_raises_sensitivity:!1,low_temptarget_lowers_sensitivity:!1,sensitivity_raises_target:!0,resistance_lowers_target:!1,exercise_mode:!1,half_basal_exercise_target:160,maxCOB:120,skip_neutral_temps:!1,unsuspend_if_no_temp:!1,bolussnooze_dia_divisor:2,min_5m_carbimpact:8,autotune_isf_adjustmentFraction:1,remainingCarbsFraction:1,remainingCarbsCap:90,enableUAM:!0,A52_risk_enable:!1,enableSMB_with_COB:!1,enableSMB_with_temptarget:!1,enableSMB_always:!1,enableSMB_after_carbs:!1,enableSMB_high_bg:!1,enableSMB_high_bg_target:110,allowSMB_with_high_temptarget:!1,maxSMBBasalMinutes:30,maxUAMSMBBasalMinutes:30,SMBInterval:3,bolus_increment:.1,maxDelta_bg_threshold:.2,curve:"rapid-acting",useCustomPeakTime:!1,insulinPeakTime:75,carbsReqThreshold:1,offline_hotspot:!1,noisyCGMTargetMultiplier:1.3,suspend_zeros_iob:!0,enableEnliteBgproxy:!1,calc_glucose_noise:!1,target_bg:!1,edison_battery_shutdown_voltage:3050,pi_battery_shutdown_percent:2}}function s(t,n,r){var f=r&&r.type?r:{max_iob:0,max_daily_safety_multiplier:3,current_basal_safety_multiplier:4,autosens_max:1.2,autosens_min:.7,rewind_resets_autosens:!0,high_temptarget_raises_sensitivity:!1,low_temptarget_lowers_sensitivity:!1,sensitivity_raises_target:!0,resistance_lowers_target:!1,exercise_mode:!1,half_basal_exercise_target:160,maxCOB:120,skip_neutral_temps:!1,unsuspend_if_no_temp:!1,bolussnooze_dia_divisor:2,min_5m_carbimpact:8,autotune_isf_adjustmentFraction:1,remainingCarbsFraction:1,remainingCarbsCap:90,enableUAM:!0,A52_risk_enable:!1,enableSMB_with_COB:!1,enableSMB_with_temptarget:!1,enableSMB_always:!1,enableSMB_after_carbs:!1,enableSMB_high_bg:!1,enableSMB_high_bg_target:110,allowSMB_with_high_temptarget:!1,maxSMBBasalMinutes:30,maxUAMSMBBasalMinutes:30,SMBInterval:3,bolus_increment:.1,maxDelta_bg_threshold:.2,curve:"rapid-acting",useCustomPeakTime:!1,insulinPeakTime:75,carbsReqThreshold:1,offline_hotspot:!1,noisyCGMTargetMultiplier:1.3,suspend_zeros_iob:!0,enableEnliteBgproxy:!1,calc_glucose_noise:!1,target_bg:!1,edison_battery_shutdown_voltage:3050,pi_battery_shutdown_percent:2};for(var l in f)n.hasOwnProperty(l)&&(f[l]=n[l]);var s=n.settings;if(!(n.settings.insulin_action_curve>1))return c(t,"DIA of",f.dia,"is not supported"),-1;if(f.dia=s.insulin_action_curve,n.model&&(f.model=n.model),f.skip_neutral_temps=n.skip_neutral_temps,f.current_basal=e.basalLookup(n.basals),f.basalprofile=n.basals,a.forEach(f.basalprofile,(function(t){t.rate=+(Math.round(t.rate+"e+3")+"e-3")})),f.max_daily_basal=e.maxDailyBasal(n),f.max_basal=e.maxBasalLookup(n),0===f.current_basal)return c(t,"current_basal of",f.current_basal,"is not supported"),-1;if(0===f.max_daily_basal)return c(t,"max_daily_basal of",f.max_daily_basal,"is not supported"),-1;if(f.max_basal<.1)return c(t,"max_basal of",f.max_basal,"is not supported"),-1;var _=u.bgTargetsLookup(t,n,f);f.out_units=n.targets.user_preferred_units,f.min_bg=Math.round(_.min_bg),f.max_bg=Math.round(_.max_bg),f.bg_targets=n.targets,a.forEach(f.bg_targets.targets,(function(t){t.high=Math.round(t.high),t.low=Math.round(t.low),t.min_bg=Math.round(t.min_bg),t.max_bg=Math.round(t.max_bg)})),delete f.bg_targets.raw,f.temptargetSet=_.temptargetSet;var h=null;return[f.sens,h]=i.isfLookup(n.isf,void 0,h),f.isfProfile=n.isf,f.sens<5?(c(t,"ISF of",f.sens,"is not supported"),-1):(void 0!==n.carbratio?(f.carb_ratio=o.carbRatioLookup(t,n,f),f.carb_ratios=n.carbratio):c(t,"Profile wasn't given carb ratio data, cannot calculate carb_ratio"),f)}s.defaults=l,s.displayedDefaults=function(t){var n={max_iob:0,max_daily_safety_multiplier:3,current_basal_safety_multiplier:4,autosens_max:1.2,autosens_min:.7,rewind_resets_autosens:!0,high_temptarget_raises_sensitivity:!1,low_temptarget_lowers_sensitivity:!1,sensitivity_raises_target:!0,resistance_lowers_target:!1,exercise_mode:!1,half_basal_exercise_target:160,maxCOB:120,skip_neutral_temps:!1,unsuspend_if_no_temp:!1,bolussnooze_dia_divisor:2,min_5m_carbimpact:8,autotune_isf_adjustmentFraction:1,remainingCarbsFraction:1,remainingCarbsCap:90,enableUAM:!0,A52_risk_enable:!1,enableSMB_with_COB:!1,enableSMB_with_temptarget:!1,enableSMB_always:!1,enableSMB_after_carbs:!1,enableSMB_high_bg:!1,enableSMB_high_bg_target:110,allowSMB_with_high_temptarget:!1,maxSMBBasalMinutes:30,maxUAMSMBBasalMinutes:30,SMBInterval:3,bolus_increment:.1,maxDelta_bg_threshold:.2,curve:"rapid-acting",useCustomPeakTime:!1,insulinPeakTime:75,carbsReqThreshold:1,offline_hotspot:!1,noisyCGMTargetMultiplier:1.3,suspend_zeros_iob:!0,enableEnliteBgproxy:!1,calc_glucose_noise:!1,target_bg:!1,edison_battery_shutdown_voltage:3050,pi_battery_shutdown_percent:2},r={};return r.max_iob=n.max_iob,r.max_daily_safety_multiplier=n.max_daily_safety_multiplier,r.current_basal_safety_multiplier=n.current_basal_safety_multiplier,r.autosens_max=n.autosens_max,r.autosens_min=n.autosens_min,r.rewind_resets_autosens=n.rewind_resets_autosens,r.exercise_mode=n.exercise_mode,r.sensitivity_raises_target=n.sensitivity_raises_target,r.unsuspend_if_no_temp=n.unsuspend_if_no_temp,r.enableSMB_with_COB=n.enableSMB_with_COB,r.enableSMB_with_temptarget=n.enableSMB_with_temptarget,r.enableUAM=n.enableUAM,r.curve=n.curve,r.offline_hotspot=n.offline_hotspot,r.edison_battery_shutdown_voltage=n.edison_battery_shutdown_voltage,r.pi_battery_shutdown_percent=n.pi_battery_shutdown_percent,c(t,r),r},t.exports=s},335:(t,n,r)=>{"use strict";var e=r(125);function u(t,n,r){var u=n;void 0===n&&(u=new Date);var i=60*u.getHours()+u.getMinutes();if(r&&i>=r.offset&&i=c.offset&&i{"use strict";var e=r(53),u=r(902).console_error;function i(t,n,r){return a(o(t,n,r))}function o(t,n,r){for(var i=n.targets,o=n.temptargets,a=new Date,f=i.targets[i.targets.length-1],c=0;c=e(i.targets[c].offset)&&a=s&&0===o[c].duration){l=f;break}if(!o[c].targetBottom||!o[c].targetTop){u(t,"eventualBG target range invalid: "+o[c].targetBottom+"-"+o[c].targetTop);break}if(a>=s&&a<_){l.high=o[c].targetTop,l.low=o[c].targetBottom,l.temptargetSet=!0;break}}return f=l}function a(t){return t.high<20&&(t.high=18*t.high),t.low<20&&(t.low=18*t.low),t.max_bg=Math.max(80,t.high),t.min_bg=Math.max(80,t.low),t.min_bg=Math.min(200,t.min_bg),t.max_bg=Math.min(200,t.max_bg),t}i.bgTargetsLookup=i,i.lookup=o,i.bound_target_range=a,t.exports=i},125:function(t,n,r){var e;t=r.nmd(t),function(){var u,i="Expected a function",o="__lodash_hash_undefined__",a="__lodash_placeholder__",f=16,c=32,l=64,s=128,_=256,h=1/0,p=9007199254740991,v=NaN,g=4294967295,d=[["ary",s],["bind",1],["bindKey",2],["curry",8],["curryRight",f],["flip",512],["partial",c],["partialRight",l],["rearg",_]],y="[object Arguments]",b="[object Array]",m="[object Boolean]",w="[object Date]",x="[object Error]",B="[object Function]",j="[object GeneratorFunction]",k="[object Map]",A="[object Number]",M="[object Object]",S="[object Promise]",O="[object RegExp]",R="[object Set]",C="[object String]",I="[object Symbol]",E="[object WeakMap]",z="[object ArrayBuffer]",T="[object DataView]",L="[object Float32Array]",D="[object Float64Array]",U="[object Int8Array]",W="[object Int16Array]",F="[object Int32Array]",P="[object Uint8Array]",$="[object Uint8ClampedArray]",N="[object Uint16Array]",q="[object Uint32Array]",Z=/\b__p \+= '';/g,G=/\b(__p \+=) '' \+/g,K=/(__e\(.*?\)|\b__t\)) \+\n'';/g,H=/&(?:amp|lt|gt|quot|#39);/g,V=/[&<>"']/g,J=RegExp(H.source),Y=RegExp(V.source),Q=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,tt=/<%=([\s\S]+?)%>/g,nt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,rt=/^\w*$/,et=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,ut=/[\\^$.*+?()[\]{}|]/g,it=RegExp(ut.source),ot=/^\s+/,at=/\s/,ft=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,ct=/\{\n\/\* \[wrapped with (.+)\] \*/,lt=/,? & /,st=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,_t=/[()=,{}\[\]\/\s]/,ht=/\\(\\)?/g,pt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,vt=/\w*$/,gt=/^[-+]0x[0-9a-f]+$/i,dt=/^0b[01]+$/i,yt=/^\[object .+?Constructor\]$/,bt=/^0o[0-7]+$/i,mt=/^(?:0|[1-9]\d*)$/,wt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,xt=/($^)/,Bt=/['\n\r\u2028\u2029\\]/g,jt="\\ud800-\\udfff",kt="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",At="\\u2700-\\u27bf",Mt="a-z\\xdf-\\xf6\\xf8-\\xff",St="A-Z\\xc0-\\xd6\\xd8-\\xde",Ot="\\ufe0e\\ufe0f",Rt="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",Ct="['’]",It="["+jt+"]",Et="["+Rt+"]",zt="["+kt+"]",Tt="\\d+",Lt="["+At+"]",Dt="["+Mt+"]",Ut="[^"+jt+Rt+Tt+At+Mt+St+"]",Wt="\\ud83c[\\udffb-\\udfff]",Ft="[^"+jt+"]",Pt="(?:\\ud83c[\\udde6-\\uddff]){2}",$t="[\\ud800-\\udbff][\\udc00-\\udfff]",Nt="["+St+"]",qt="\\u200d",Zt="(?:"+Dt+"|"+Ut+")",Gt="(?:"+Nt+"|"+Ut+")",Kt="(?:['’](?:d|ll|m|re|s|t|ve))?",Ht="(?:['’](?:D|LL|M|RE|S|T|VE))?",Vt="(?:"+zt+"|"+Wt+")"+"?",Jt="["+Ot+"]?",Yt=Jt+Vt+("(?:"+qt+"(?:"+[Ft,Pt,$t].join("|")+")"+Jt+Vt+")*"),Qt="(?:"+[Lt,Pt,$t].join("|")+")"+Yt,Xt="(?:"+[Ft+zt+"?",zt,Pt,$t,It].join("|")+")",tn=RegExp(Ct,"g"),nn=RegExp(zt,"g"),rn=RegExp(Wt+"(?="+Wt+")|"+Xt+Yt,"g"),en=RegExp([Nt+"?"+Dt+"+"+Kt+"(?="+[Et,Nt,"$"].join("|")+")",Gt+"+"+Ht+"(?="+[Et,Nt+Zt,"$"].join("|")+")",Nt+"?"+Zt+"+"+Kt,Nt+"+"+Ht,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",Tt,Qt].join("|"),"g"),un=RegExp("["+qt+jt+kt+Ot+"]"),on=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,an=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],fn=-1,cn={};cn[L]=cn[D]=cn[U]=cn[W]=cn[F]=cn[P]=cn[$]=cn[N]=cn[q]=!0,cn[y]=cn[b]=cn[z]=cn[m]=cn[T]=cn[w]=cn[x]=cn[B]=cn[k]=cn[A]=cn[M]=cn[O]=cn[R]=cn[C]=cn[E]=!1;var ln={};ln[y]=ln[b]=ln[z]=ln[T]=ln[m]=ln[w]=ln[L]=ln[D]=ln[U]=ln[W]=ln[F]=ln[k]=ln[A]=ln[M]=ln[O]=ln[R]=ln[C]=ln[I]=ln[P]=ln[$]=ln[N]=ln[q]=!0,ln[x]=ln[B]=ln[E]=!1;var sn={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},_n=parseFloat,hn=parseInt,pn="object"==typeof r.g&&r.g&&r.g.Object===Object&&r.g,vn="object"==typeof self&&self&&self.Object===Object&&self,gn=pn||vn||Function("return this")(),dn=n&&!n.nodeType&&n,yn=dn&&t&&!t.nodeType&&t,bn=yn&&yn.exports===dn,mn=bn&&pn.process,wn=function(){try{var t=yn&&yn.require&&yn.require("util").types;return t||mn&&mn.binding&&mn.binding("util")}catch(t){}}(),xn=wn&&wn.isArrayBuffer,Bn=wn&&wn.isDate,jn=wn&&wn.isMap,kn=wn&&wn.isRegExp,An=wn&&wn.isSet,Mn=wn&&wn.isTypedArray;function Sn(t,n,r){switch(r.length){case 0:return t.call(n);case 1:return t.call(n,r[0]);case 2:return t.call(n,r[0],r[1]);case 3:return t.call(n,r[0],r[1],r[2])}return t.apply(n,r)}function On(t,n,r,e){for(var u=-1,i=null==t?0:t.length;++u-1}function Tn(t,n,r){for(var e=-1,u=null==t?0:t.length;++e-1;);return r}function ur(t,n){for(var r=t.length;r--&&qn(n,t[r],0)>-1;);return r}var ir=Vn({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"}),or=Vn({"&":"&","<":"<",">":">",'"':""","'":"'"});function ar(t){return"\\"+sn[t]}function fr(t){return un.test(t)}function cr(t){var n=-1,r=Array(t.size);return t.forEach((function(t,e){r[++n]=[e,t]})),r}function lr(t,n){return function(r){return t(n(r))}}function sr(t,n){for(var r=-1,e=t.length,u=0,i=[];++r",""":'"',"'":"'"});var yr=function t(n){var r,e=(n=null==n?gn:yr.defaults(gn.Object(),n,yr.pick(gn,an))).Array,at=n.Date,jt=n.Error,kt=n.Function,At=n.Math,Mt=n.Object,St=n.RegExp,Ot=n.String,Rt=n.TypeError,Ct=e.prototype,It=kt.prototype,Et=Mt.prototype,zt=n["__core-js_shared__"],Tt=It.toString,Lt=Et.hasOwnProperty,Dt=0,Ut=(r=/[^.]+$/.exec(zt&&zt.keys&&zt.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"",Wt=Et.toString,Ft=Tt.call(Mt),Pt=gn._,$t=St("^"+Tt.call(Lt).replace(ut,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Nt=bn?n.Buffer:u,qt=n.Symbol,Zt=n.Uint8Array,Gt=Nt?Nt.allocUnsafe:u,Kt=lr(Mt.getPrototypeOf,Mt),Ht=Mt.create,Vt=Et.propertyIsEnumerable,Jt=Ct.splice,Yt=qt?qt.isConcatSpreadable:u,Qt=qt?qt.iterator:u,Xt=qt?qt.toStringTag:u,rn=function(){try{var t=_i(Mt,"defineProperty");return t({},"",{}),t}catch(t){}}(),un=n.clearTimeout!==gn.clearTimeout&&n.clearTimeout,sn=at&&at.now!==gn.Date.now&&at.now,pn=n.setTimeout!==gn.setTimeout&&n.setTimeout,vn=At.ceil,dn=At.floor,yn=Mt.getOwnPropertySymbols,mn=Nt?Nt.isBuffer:u,wn=n.isFinite,Pn=Ct.join,Vn=lr(Mt.keys,Mt),br=At.max,mr=At.min,wr=at.now,xr=n.parseInt,Br=At.random,jr=Ct.reverse,kr=_i(n,"DataView"),Ar=_i(n,"Map"),Mr=_i(n,"Promise"),Sr=_i(n,"Set"),Or=_i(n,"WeakMap"),Rr=_i(Mt,"create"),Cr=Or&&new Or,Ir={},Er=Ui(kr),zr=Ui(Ar),Tr=Ui(Mr),Lr=Ui(Sr),Dr=Ui(Or),Ur=qt?qt.prototype:u,Wr=Ur?Ur.valueOf:u,Fr=Ur?Ur.toString:u;function Pr(t){if(ra(t)&&!Zo(t)&&!(t instanceof Zr)){if(t instanceof qr)return t;if(Lt.call(t,"__wrapped__"))return Wi(t)}return new qr(t)}var $r=function(){function t(){}return function(n){if(!na(n))return{};if(Ht)return Ht(n);t.prototype=n;var r=new t;return t.prototype=u,r}}();function Nr(){}function qr(t,n){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!n,this.__index__=0,this.__values__=u}function Zr(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=g,this.__views__=[]}function Gr(t){var n=-1,r=null==t?0:t.length;for(this.clear();++n=n?t:n)),t}function ce(t,n,r,e,i,o){var a,f=1&n,c=2&n,l=4&n;if(r&&(a=i?r(t,e,i,o):r(t)),a!==u)return a;if(!na(t))return t;var s=Zo(t);if(s){if(a=function(t){var n=t.length,r=new t.constructor(n);n&&"string"==typeof t[0]&&Lt.call(t,"index")&&(r.index=t.index,r.input=t.input);return r}(t),!f)return Ru(t,a)}else{var _=vi(t),h=_==B||_==j;if(Vo(t))return ju(t,f);if(_==M||_==y||h&&!i){if(a=c||h?{}:di(t),!f)return c?function(t,n){return Cu(t,pi(t),n)}(t,function(t,n){return t&&Cu(n,Ea(n),t)}(a,t)):function(t,n){return Cu(t,hi(t),n)}(t,ie(a,t))}else{if(!ln[_])return i?t:{};a=function(t,n,r){var e=t.constructor;switch(n){case z:return ku(t);case m:case w:return new e(+t);case T:return function(t,n){var r=n?ku(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.byteLength)}(t,r);case L:case D:case U:case W:case F:case P:case $:case N:case q:return Au(t,r);case k:return new e;case A:case C:return new e(t);case O:return function(t){var n=new t.constructor(t.source,vt.exec(t));return n.lastIndex=t.lastIndex,n}(t);case R:return new e;case I:return u=t,Wr?Mt(Wr.call(u)):{}}var u}(t,_,f)}}o||(o=new Jr);var p=o.get(t);if(p)return p;o.set(t,a),aa(t)?t.forEach((function(e){a.add(ce(e,n,r,e,t,o))})):ea(t)&&t.forEach((function(e,u){a.set(u,ce(e,n,r,u,t,o))}));var v=s?u:(l?c?ii:ui:c?Ea:Ia)(t);return Rn(v||t,(function(e,u){v&&(e=t[u=e]),re(a,u,ce(e,n,r,u,t,o))})),a}function le(t,n,r){var e=r.length;if(null==t)return!e;for(t=Mt(t);e--;){var i=r[e],o=n[i],a=t[i];if(a===u&&!(i in t)||!o(a))return!1}return!0}function se(t,n,r){if("function"!=typeof t)throw new Rt(i);return Ci((function(){t.apply(u,r)}),n)}function _e(t,n,r,e){var u=-1,i=zn,o=!0,a=t.length,f=[],c=n.length;if(!a)return f;r&&(n=Ln(n,tr(r))),e?(i=Tn,o=!1):n.length>=200&&(i=rr,o=!1,n=new Vr(n));t:for(;++u-1},Kr.prototype.set=function(t,n){var r=this.__data__,e=ee(r,t);return e<0?(++this.size,r.push([t,n])):r[e][1]=n,this},Hr.prototype.clear=function(){this.size=0,this.__data__={hash:new Gr,map:new(Ar||Kr),string:new Gr}},Hr.prototype.delete=function(t){var n=li(this,t).delete(t);return this.size-=n?1:0,n},Hr.prototype.get=function(t){return li(this,t).get(t)},Hr.prototype.has=function(t){return li(this,t).has(t)},Hr.prototype.set=function(t,n){var r=li(this,t),e=r.size;return r.set(t,n),this.size+=r.size==e?0:1,this},Vr.prototype.add=Vr.prototype.push=function(t){return this.__data__.set(t,o),this},Vr.prototype.has=function(t){return this.__data__.has(t)},Jr.prototype.clear=function(){this.__data__=new Kr,this.size=0},Jr.prototype.delete=function(t){var n=this.__data__,r=n.delete(t);return this.size=n.size,r},Jr.prototype.get=function(t){return this.__data__.get(t)},Jr.prototype.has=function(t){return this.__data__.has(t)},Jr.prototype.set=function(t,n){var r=this.__data__;if(r instanceof Kr){var e=r.__data__;if(!Ar||e.length<199)return e.push([t,n]),this.size=++r.size,this;r=this.__data__=new Hr(e)}return r.set(t,n),this.size=r.size,this};var he=zu(we),pe=zu(xe,!0);function ve(t,n){var r=!0;return he(t,(function(t,e,u){return r=!!n(t,e,u)})),r}function ge(t,n,r){for(var e=-1,i=t.length;++e0&&r(a)?n>1?ye(a,n-1,r,e,u):Dn(u,a):e||(u[u.length]=a)}return u}var be=Tu(),me=Tu(!0);function we(t,n){return t&&be(t,n,Ia)}function xe(t,n){return t&&me(t,n,Ia)}function Be(t,n){return En(n,(function(n){return Qo(t[n])}))}function je(t,n){for(var r=0,e=(n=mu(n,t)).length;null!=t&&rn}function Se(t,n){return null!=t&&Lt.call(t,n)}function Oe(t,n){return null!=t&&n in Mt(t)}function Re(t,n,r){for(var i=r?Tn:zn,o=t[0].length,a=t.length,f=a,c=e(a),l=1/0,s=[];f--;){var _=t[f];f&&n&&(_=Ln(_,tr(n))),l=mr(_.length,l),c[f]=!r&&(n||o>=120&&_.length>=120)?new Vr(f&&_):u}_=t[0];var h=-1,p=c[0];t:for(;++h=a?f:f*("desc"==r[e]?-1:1)}return t.index-n.index}(t,n,r)}))}function Ge(t,n,r){for(var e=-1,u=n.length,i={};++e-1;)a!==t&&Jt.call(a,f,1),Jt.call(t,f,1);return t}function He(t,n){for(var r=t?n.length:0,e=r-1;r--;){var u=n[r];if(r==e||u!==i){var i=u;bi(u)?Jt.call(t,u,1):_u(t,u)}}return t}function Ve(t,n){return t+dn(Br()*(n-t+1))}function Je(t,n){var r="";if(!t||n<1||n>p)return r;do{n%2&&(r+=t),(n=dn(n/2))&&(t+=t)}while(n);return r}function Ye(t,n){return Ii(Mi(t,n,uf),t+"")}function Qe(t){return Qr(Pa(t))}function Xe(t,n){var r=Pa(t);return Ti(r,fe(n,0,r.length))}function tu(t,n,r,e){if(!na(t))return t;for(var i=-1,o=(n=mu(n,t)).length,a=o-1,f=t;null!=f&&++ii?0:i+n),(r=r>i?i:r)<0&&(r+=i),i=n>r?0:r-n>>>0,n>>>=0;for(var o=e(i);++u>>1,o=t[i];null!==o&&!ca(o)&&(r?o<=n:o=200){var c=n?null:Ju(t);if(c)return _r(c);o=!1,u=rr,f=new Vr}else f=n?[]:a;t:for(;++e=e?t:uu(t,n,r)}var Bu=un||function(t){return gn.clearTimeout(t)};function ju(t,n){if(n)return t.slice();var r=t.length,e=Gt?Gt(r):new t.constructor(r);return t.copy(e),e}function ku(t){var n=new t.constructor(t.byteLength);return new Zt(n).set(new Zt(t)),n}function Au(t,n){var r=n?ku(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.length)}function Mu(t,n){if(t!==n){var r=t!==u,e=null===t,i=t==t,o=ca(t),a=n!==u,f=null===n,c=n==n,l=ca(n);if(!f&&!l&&!o&&t>n||o&&a&&c&&!f&&!l||e&&a&&c||!r&&c||!i)return 1;if(!e&&!o&&!l&&t1?r[i-1]:u,a=i>2?r[2]:u;for(o=t.length>3&&"function"==typeof o?(i--,o):u,a&&mi(r[0],r[1],a)&&(o=i<3?u:o,i=1),n=Mt(n);++e-1?i[o?n[a]:a]:u}}function Fu(t){return ei((function(n){var r=n.length,e=r,o=qr.prototype.thru;for(t&&n.reverse();e--;){var a=n[e];if("function"!=typeof a)throw new Rt(i);if(o&&!f&&"wrapper"==ai(a))var f=new qr([],!0)}for(e=f?e:r;++e1&&m.reverse(),h&&lf))return!1;var l=o.get(t),s=o.get(n);if(l&&s)return l==n&&s==t;var _=-1,h=!0,p=2&r?new Vr:u;for(o.set(t,n),o.set(n,t);++_-1&&t%1==0&&t1?"& ":"")+n[e],n=n.join(r>2?", ":" "),t.replace(ft,"{\n/* [wrapped with "+n+"] */\n")}(e,function(t,n){return Rn(d,(function(r){var e="_."+r[0];n&r[1]&&!zn(t,e)&&t.push(e)})),t.sort()}(function(t){var n=t.match(ct);return n?n[1].split(lt):[]}(e),r)))}function zi(t){var n=0,r=0;return function(){var e=wr(),i=16-(e-r);if(r=e,i>0){if(++n>=800)return arguments[0]}else n=0;return t.apply(u,arguments)}}function Ti(t,n){var r=-1,e=t.length,i=e-1;for(n=n===u?e:n;++r1?t[n-1]:u;return r="function"==typeof r?(t.pop(),r):u,io(t,r)}));function _o(t){var n=Pr(t);return n.__chain__=!0,n}function ho(t,n){return n(t)}var po=ei((function(t){var n=t.length,r=n?t[0]:0,e=this.__wrapped__,i=function(n){return ae(n,t)};return!(n>1||this.__actions__.length)&&e instanceof Zr&&bi(r)?((e=e.slice(r,+r+(n?1:0))).__actions__.push({func:ho,args:[i],thisArg:u}),new qr(e,this.__chain__).thru((function(t){return n&&!t.length&&t.push(u),t}))):this.thru(i)}));var vo=Iu((function(t,n,r){Lt.call(t,r)?++t[r]:oe(t,r,1)}));var go=Wu(Ni),yo=Wu(qi);function bo(t,n){return(Zo(t)?Rn:he)(t,ci(n,3))}function mo(t,n){return(Zo(t)?Cn:pe)(t,ci(n,3))}var wo=Iu((function(t,n,r){Lt.call(t,r)?t[r].push(n):oe(t,r,[n])}));var xo=Ye((function(t,n,r){var u=-1,i="function"==typeof n,o=Ko(t)?e(t.length):[];return he(t,(function(t){o[++u]=i?Sn(n,t,r):Ce(t,n,r)})),o})),Bo=Iu((function(t,n,r){oe(t,r,n)}));function jo(t,n){return(Zo(t)?Ln:Fe)(t,ci(n,3))}var ko=Iu((function(t,n,r){t[r?0:1].push(n)}),(function(){return[[],[]]}));var Ao=Ye((function(t,n){if(null==t)return[];var r=n.length;return r>1&&mi(t,n[0],n[1])?n=[]:r>2&&mi(n[0],n[1],n[2])&&(n=[n[0]]),Ze(t,ye(n,1),[])})),Mo=sn||function(){return gn.Date.now()};function So(t,n,r){return n=r?u:n,n=t&&null==n?t.length:n,Qu(t,s,u,u,u,u,n)}function Oo(t,n){var r;if("function"!=typeof n)throw new Rt(i);return t=va(t),function(){return--t>0&&(r=n.apply(this,arguments)),t<=1&&(n=u),r}}var Ro=Ye((function(t,n,r){var e=1;if(r.length){var u=sr(r,fi(Ro));e|=c}return Qu(t,e,n,r,u)})),Co=Ye((function(t,n,r){var e=3;if(r.length){var u=sr(r,fi(Co));e|=c}return Qu(n,e,t,r,u)}));function Io(t,n,r){var e,o,a,f,c,l,s=0,_=!1,h=!1,p=!0;if("function"!=typeof t)throw new Rt(i);function v(n){var r=e,i=o;return e=o=u,s=n,f=t.apply(i,r)}function g(t){var r=t-l;return l===u||r>=n||r<0||h&&t-s>=a}function d(){var t=Mo();if(g(t))return y(t);c=Ci(d,function(t){var r=n-(t-l);return h?mr(r,a-(t-s)):r}(t))}function y(t){return c=u,p&&e?v(t):(e=o=u,f)}function b(){var t=Mo(),r=g(t);if(e=arguments,o=this,l=t,r){if(c===u)return function(t){return s=t,c=Ci(d,n),_?v(t):f}(l);if(h)return Bu(c),c=Ci(d,n),v(l)}return c===u&&(c=Ci(d,n)),f}return n=da(n)||0,na(r)&&(_=!!r.leading,a=(h="maxWait"in r)?br(da(r.maxWait)||0,n):a,p="trailing"in r?!!r.trailing:p),b.cancel=function(){c!==u&&Bu(c),s=0,e=l=o=c=u},b.flush=function(){return c===u?f:y(Mo())},b}var Eo=Ye((function(t,n){return se(t,1,n)})),zo=Ye((function(t,n,r){return se(t,da(n)||0,r)}));function To(t,n){if("function"!=typeof t||null!=n&&"function"!=typeof n)throw new Rt(i);var r=function(){var e=arguments,u=n?n.apply(this,e):e[0],i=r.cache;if(i.has(u))return i.get(u);var o=t.apply(this,e);return r.cache=i.set(u,o)||i,o};return r.cache=new(To.Cache||Hr),r}function Lo(t){if("function"!=typeof t)throw new Rt(i);return function(){var n=arguments;switch(n.length){case 0:return!t.call(this);case 1:return!t.call(this,n[0]);case 2:return!t.call(this,n[0],n[1]);case 3:return!t.call(this,n[0],n[1],n[2])}return!t.apply(this,n)}}To.Cache=Hr;var Do=wu((function(t,n){var r=(n=1==n.length&&Zo(n[0])?Ln(n[0],tr(ci())):Ln(ye(n,1),tr(ci()))).length;return Ye((function(e){for(var u=-1,i=mr(e.length,r);++u=n})),qo=Ie(function(){return arguments}())?Ie:function(t){return ra(t)&&Lt.call(t,"callee")&&!Vt.call(t,"callee")},Zo=e.isArray,Go=xn?tr(xn):function(t){return ra(t)&&Ae(t)==z};function Ko(t){return null!=t&&ta(t.length)&&!Qo(t)}function Ho(t){return ra(t)&&Ko(t)}var Vo=mn||yf,Jo=Bn?tr(Bn):function(t){return ra(t)&&Ae(t)==w};function Yo(t){if(!ra(t))return!1;var n=Ae(t);return n==x||"[object DOMException]"==n||"string"==typeof t.message&&"string"==typeof t.name&&!ia(t)}function Qo(t){if(!na(t))return!1;var n=Ae(t);return n==B||n==j||"[object AsyncFunction]"==n||"[object Proxy]"==n}function Xo(t){return"number"==typeof t&&t==va(t)}function ta(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=p}function na(t){var n=typeof t;return null!=t&&("object"==n||"function"==n)}function ra(t){return null!=t&&"object"==typeof t}var ea=jn?tr(jn):function(t){return ra(t)&&vi(t)==k};function ua(t){return"number"==typeof t||ra(t)&&Ae(t)==A}function ia(t){if(!ra(t)||Ae(t)!=M)return!1;var n=Kt(t);if(null===n)return!0;var r=Lt.call(n,"constructor")&&n.constructor;return"function"==typeof r&&r instanceof r&&Tt.call(r)==Ft}var oa=kn?tr(kn):function(t){return ra(t)&&Ae(t)==O};var aa=An?tr(An):function(t){return ra(t)&&vi(t)==R};function fa(t){return"string"==typeof t||!Zo(t)&&ra(t)&&Ae(t)==C}function ca(t){return"symbol"==typeof t||ra(t)&&Ae(t)==I}var la=Mn?tr(Mn):function(t){return ra(t)&&ta(t.length)&&!!cn[Ae(t)]};var sa=Ku(We),_a=Ku((function(t,n){return t<=n}));function ha(t){if(!t)return[];if(Ko(t))return fa(t)?vr(t):Ru(t);if(Qt&&t[Qt])return function(t){for(var n,r=[];!(n=t.next()).done;)r.push(n.value);return r}(t[Qt]());var n=vi(t);return(n==k?cr:n==R?_r:Pa)(t)}function pa(t){return t?(t=da(t))===h||t===-1/0?17976931348623157e292*(t<0?-1:1):t==t?t:0:0===t?t:0}function va(t){var n=pa(t),r=n%1;return n==n?r?n-r:n:0}function ga(t){return t?fe(va(t),0,g):0}function da(t){if("number"==typeof t)return t;if(ca(t))return v;if(na(t)){var n="function"==typeof t.valueOf?t.valueOf():t;t=na(n)?n+"":n}if("string"!=typeof t)return 0===t?t:+t;t=Xn(t);var r=dt.test(t);return r||bt.test(t)?hn(t.slice(2),r?2:8):gt.test(t)?v:+t}function ya(t){return Cu(t,Ea(t))}function ba(t){return null==t?"":lu(t)}var ma=Eu((function(t,n){if(ji(n)||Ko(n))Cu(n,Ia(n),t);else for(var r in n)Lt.call(n,r)&&re(t,r,n[r])})),wa=Eu((function(t,n){Cu(n,Ea(n),t)})),xa=Eu((function(t,n,r,e){Cu(n,Ea(n),t,e)})),Ba=Eu((function(t,n,r,e){Cu(n,Ia(n),t,e)})),ja=ei(ae);var ka=Ye((function(t,n){t=Mt(t);var r=-1,e=n.length,i=e>2?n[2]:u;for(i&&mi(n[0],n[1],i)&&(e=1);++r1),n})),Cu(t,ii(t),r),e&&(r=ce(r,7,ni));for(var u=n.length;u--;)_u(r,n[u]);return r}));var Da=ei((function(t,n){return null==t?{}:function(t,n){return Ge(t,n,(function(n,r){return Sa(t,r)}))}(t,n)}));function Ua(t,n){if(null==t)return{};var r=Ln(ii(t),(function(t){return[t]}));return n=ci(n),Ge(t,r,(function(t,r){return n(t,r[0])}))}var Wa=Yu(Ia),Fa=Yu(Ea);function Pa(t){return null==t?[]:nr(t,Ia(t))}var $a=Du((function(t,n,r){return n=n.toLowerCase(),t+(r?Na(n):n)}));function Na(t){return Ya(ba(t).toLowerCase())}function qa(t){return(t=ba(t))&&t.replace(wt,ir).replace(nn,"")}var Za=Du((function(t,n,r){return t+(r?"-":"")+n.toLowerCase()})),Ga=Du((function(t,n,r){return t+(r?" ":"")+n.toLowerCase()})),Ka=Lu("toLowerCase");var Ha=Du((function(t,n,r){return t+(r?"_":"")+n.toLowerCase()}));var Va=Du((function(t,n,r){return t+(r?" ":"")+Ya(n)}));var Ja=Du((function(t,n,r){return t+(r?" ":"")+n.toUpperCase()})),Ya=Lu("toUpperCase");function Qa(t,n,r){return t=ba(t),(n=r?u:n)===u?function(t){return on.test(t)}(t)?function(t){return t.match(en)||[]}(t):function(t){return t.match(st)||[]}(t):t.match(n)||[]}var Xa=Ye((function(t,n){try{return Sn(t,u,n)}catch(t){return Yo(t)?t:new jt(t)}})),tf=ei((function(t,n){return Rn(n,(function(n){n=Di(n),oe(t,n,Ro(t[n],t))})),t}));function nf(t){return function(){return t}}var rf=Fu(),ef=Fu(!0);function uf(t){return t}function of(t){return Le("function"==typeof t?t:ce(t,1))}var af=Ye((function(t,n){return function(r){return Ce(r,t,n)}})),ff=Ye((function(t,n){return function(r){return Ce(t,r,n)}}));function cf(t,n,r){var e=Ia(n),u=Be(n,e);null!=r||na(n)&&(u.length||!e.length)||(r=n,n=t,t=this,u=Be(n,Ia(n)));var i=!(na(r)&&"chain"in r&&!r.chain),o=Qo(t);return Rn(u,(function(r){var e=n[r];t[r]=e,o&&(t.prototype[r]=function(){var n=this.__chain__;if(i||n){var r=t(this.__wrapped__);return(r.__actions__=Ru(this.__actions__)).push({func:e,args:arguments,thisArg:t}),r.__chain__=n,r}return e.apply(t,Dn([this.value()],arguments))})})),t}function lf(){}var sf=qu(Ln),_f=qu(In),hf=qu(Fn);function pf(t){return wi(t)?Hn(Di(t)):function(t){return function(n){return je(n,t)}}(t)}var vf=Gu(),gf=Gu(!0);function df(){return[]}function yf(){return!1}var bf=Nu((function(t,n){return t+n}),0),mf=Vu("ceil"),wf=Nu((function(t,n){return t/n}),1),xf=Vu("floor");var Bf,jf=Nu((function(t,n){return t*n}),1),kf=Vu("round"),Af=Nu((function(t,n){return t-n}),0);return Pr.after=function(t,n){if("function"!=typeof n)throw new Rt(i);return t=va(t),function(){if(--t<1)return n.apply(this,arguments)}},Pr.ary=So,Pr.assign=ma,Pr.assignIn=wa,Pr.assignInWith=xa,Pr.assignWith=Ba,Pr.at=ja,Pr.before=Oo,Pr.bind=Ro,Pr.bindAll=tf,Pr.bindKey=Co,Pr.castArray=function(){if(!arguments.length)return[];var t=arguments[0];return Zo(t)?t:[t]},Pr.chain=_o,Pr.chunk=function(t,n,r){n=(r?mi(t,n,r):n===u)?1:br(va(n),0);var i=null==t?0:t.length;if(!i||n<1)return[];for(var o=0,a=0,f=e(vn(i/n));oi?0:i+r),(e=e===u||e>i?i:va(e))<0&&(e+=i),e=r>e?0:ga(e);r>>0)?(t=ba(t))&&("string"==typeof n||null!=n&&!oa(n))&&!(n=lu(n))&&fr(t)?xu(vr(t),0,r):t.split(n,r):[]},Pr.spread=function(t,n){if("function"!=typeof t)throw new Rt(i);return n=null==n?0:br(va(n),0),Ye((function(r){var e=r[n],u=xu(r,0,n);return e&&Dn(u,e),Sn(t,this,u)}))},Pr.tail=function(t){var n=null==t?0:t.length;return n?uu(t,1,n):[]},Pr.take=function(t,n,r){return t&&t.length?uu(t,0,(n=r||n===u?1:va(n))<0?0:n):[]},Pr.takeRight=function(t,n,r){var e=null==t?0:t.length;return e?uu(t,(n=e-(n=r||n===u?1:va(n)))<0?0:n,e):[]},Pr.takeRightWhile=function(t,n){return t&&t.length?pu(t,ci(n,3),!1,!0):[]},Pr.takeWhile=function(t,n){return t&&t.length?pu(t,ci(n,3)):[]},Pr.tap=function(t,n){return n(t),t},Pr.throttle=function(t,n,r){var e=!0,u=!0;if("function"!=typeof t)throw new Rt(i);return na(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),Io(t,n,{leading:e,maxWait:n,trailing:u})},Pr.thru=ho,Pr.toArray=ha,Pr.toPairs=Wa,Pr.toPairsIn=Fa,Pr.toPath=function(t){return Zo(t)?Ln(t,Di):ca(t)?[t]:Ru(Li(ba(t)))},Pr.toPlainObject=ya,Pr.transform=function(t,n,r){var e=Zo(t),u=e||Vo(t)||la(t);if(n=ci(n,4),null==r){var i=t&&t.constructor;r=u?e?new i:[]:na(t)&&Qo(i)?$r(Kt(t)):{}}return(u?Rn:we)(t,(function(t,e,u){return n(r,t,e,u)})),r},Pr.unary=function(t){return So(t,1)},Pr.union=no,Pr.unionBy=ro,Pr.unionWith=eo,Pr.uniq=function(t){return t&&t.length?su(t):[]},Pr.uniqBy=function(t,n){return t&&t.length?su(t,ci(n,2)):[]},Pr.uniqWith=function(t,n){return n="function"==typeof n?n:u,t&&t.length?su(t,u,n):[]},Pr.unset=function(t,n){return null==t||_u(t,n)},Pr.unzip=uo,Pr.unzipWith=io,Pr.update=function(t,n,r){return null==t?t:hu(t,n,bu(r))},Pr.updateWith=function(t,n,r,e){return e="function"==typeof e?e:u,null==t?t:hu(t,n,bu(r),e)},Pr.values=Pa,Pr.valuesIn=function(t){return null==t?[]:nr(t,Ea(t))},Pr.without=oo,Pr.words=Qa,Pr.wrap=function(t,n){return Uo(bu(n),t)},Pr.xor=ao,Pr.xorBy=fo,Pr.xorWith=co,Pr.zip=lo,Pr.zipObject=function(t,n){return du(t||[],n||[],re)},Pr.zipObjectDeep=function(t,n){return du(t||[],n||[],tu)},Pr.zipWith=so,Pr.entries=Wa,Pr.entriesIn=Fa,Pr.extend=wa,Pr.extendWith=xa,cf(Pr,Pr),Pr.add=bf,Pr.attempt=Xa,Pr.camelCase=$a,Pr.capitalize=Na,Pr.ceil=mf,Pr.clamp=function(t,n,r){return r===u&&(r=n,n=u),r!==u&&(r=(r=da(r))==r?r:0),n!==u&&(n=(n=da(n))==n?n:0),fe(da(t),n,r)},Pr.clone=function(t){return ce(t,4)},Pr.cloneDeep=function(t){return ce(t,5)},Pr.cloneDeepWith=function(t,n){return ce(t,5,n="function"==typeof n?n:u)},Pr.cloneWith=function(t,n){return ce(t,4,n="function"==typeof n?n:u)},Pr.conformsTo=function(t,n){return null==n||le(t,n,Ia(n))},Pr.deburr=qa,Pr.defaultTo=function(t,n){return null==t||t!=t?n:t},Pr.divide=wf,Pr.endsWith=function(t,n,r){t=ba(t),n=lu(n);var e=t.length,i=r=r===u?e:fe(va(r),0,e);return(r-=n.length)>=0&&t.slice(r,i)==n},Pr.eq=Po,Pr.escape=function(t){return(t=ba(t))&&Y.test(t)?t.replace(V,or):t},Pr.escapeRegExp=function(t){return(t=ba(t))&&it.test(t)?t.replace(ut,"\\$&"):t},Pr.every=function(t,n,r){var e=Zo(t)?In:ve;return r&&mi(t,n,r)&&(n=u),e(t,ci(n,3))},Pr.find=go,Pr.findIndex=Ni,Pr.findKey=function(t,n){return $n(t,ci(n,3),we)},Pr.findLast=yo,Pr.findLastIndex=qi,Pr.findLastKey=function(t,n){return $n(t,ci(n,3),xe)},Pr.floor=xf,Pr.forEach=bo,Pr.forEachRight=mo,Pr.forIn=function(t,n){return null==t?t:be(t,ci(n,3),Ea)},Pr.forInRight=function(t,n){return null==t?t:me(t,ci(n,3),Ea)},Pr.forOwn=function(t,n){return t&&we(t,ci(n,3))},Pr.forOwnRight=function(t,n){return t&&xe(t,ci(n,3))},Pr.get=Ma,Pr.gt=$o,Pr.gte=No,Pr.has=function(t,n){return null!=t&&gi(t,n,Se)},Pr.hasIn=Sa,Pr.head=Gi,Pr.identity=uf,Pr.includes=function(t,n,r,e){t=Ko(t)?t:Pa(t),r=r&&!e?va(r):0;var u=t.length;return r<0&&(r=br(u+r,0)),fa(t)?r<=u&&t.indexOf(n,r)>-1:!!u&&qn(t,n,r)>-1},Pr.indexOf=function(t,n,r){var e=null==t?0:t.length;if(!e)return-1;var u=null==r?0:va(r);return u<0&&(u=br(e+u,0)),qn(t,n,u)},Pr.inRange=function(t,n,r){return n=pa(n),r===u?(r=n,n=0):r=pa(r),function(t,n,r){return t>=mr(n,r)&&t=-9007199254740991&&t<=p},Pr.isSet=aa,Pr.isString=fa,Pr.isSymbol=ca,Pr.isTypedArray=la,Pr.isUndefined=function(t){return t===u},Pr.isWeakMap=function(t){return ra(t)&&vi(t)==E},Pr.isWeakSet=function(t){return ra(t)&&"[object WeakSet]"==Ae(t)},Pr.join=function(t,n){return null==t?"":Pn.call(t,n)},Pr.kebabCase=Za,Pr.last=Ji,Pr.lastIndexOf=function(t,n,r){var e=null==t?0:t.length;if(!e)return-1;var i=e;return r!==u&&(i=(i=va(r))<0?br(e+i,0):mr(i,e-1)),n==n?function(t,n,r){for(var e=r+1;e--;)if(t[e]===n)return e;return e}(t,n,i):Nn(t,Gn,i,!0)},Pr.lowerCase=Ga,Pr.lowerFirst=Ka,Pr.lt=sa,Pr.lte=_a,Pr.max=function(t){return t&&t.length?ge(t,uf,Me):u},Pr.maxBy=function(t,n){return t&&t.length?ge(t,ci(n,2),Me):u},Pr.mean=function(t){return Kn(t,uf)},Pr.meanBy=function(t,n){return Kn(t,ci(n,2))},Pr.min=function(t){return t&&t.length?ge(t,uf,We):u},Pr.minBy=function(t,n){return t&&t.length?ge(t,ci(n,2),We):u},Pr.stubArray=df,Pr.stubFalse=yf,Pr.stubObject=function(){return{}},Pr.stubString=function(){return""},Pr.stubTrue=function(){return!0},Pr.multiply=jf,Pr.nth=function(t,n){return t&&t.length?qe(t,va(n)):u},Pr.noConflict=function(){return gn._===this&&(gn._=Pt),this},Pr.noop=lf,Pr.now=Mo,Pr.pad=function(t,n,r){t=ba(t);var e=(n=va(n))?pr(t):0;if(!n||e>=n)return t;var u=(n-e)/2;return Zu(dn(u),r)+t+Zu(vn(u),r)},Pr.padEnd=function(t,n,r){t=ba(t);var e=(n=va(n))?pr(t):0;return n&&en){var e=t;t=n,n=e}if(r||t%1||n%1){var i=Br();return mr(t+i*(n-t+_n("1e-"+((i+"").length-1))),n)}return Ve(t,n)},Pr.reduce=function(t,n,r){var e=Zo(t)?Un:Jn,u=arguments.length<3;return e(t,ci(n,4),r,u,he)},Pr.reduceRight=function(t,n,r){var e=Zo(t)?Wn:Jn,u=arguments.length<3;return e(t,ci(n,4),r,u,pe)},Pr.repeat=function(t,n,r){return n=(r?mi(t,n,r):n===u)?1:va(n),Je(ba(t),n)},Pr.replace=function(){var t=arguments,n=ba(t[0]);return t.length<3?n:n.replace(t[1],t[2])},Pr.result=function(t,n,r){var e=-1,i=(n=mu(n,t)).length;for(i||(i=1,t=u);++ep)return[];var r=g,e=mr(t,g);n=ci(n),t-=g;for(var u=Qn(e,n);++r=o)return t;var f=r-pr(e);if(f<1)return e;var c=a?xu(a,0,f).join(""):t.slice(0,f);if(i===u)return c+e;if(a&&(f+=c.length-f),oa(i)){if(t.slice(f).search(i)){var l,s=c;for(i.global||(i=St(i.source,ba(vt.exec(i))+"g")),i.lastIndex=0;l=i.exec(s);)var _=l.index;c=c.slice(0,_===u?f:_)}}else if(t.indexOf(lu(i),f)!=f){var h=c.lastIndexOf(i);h>-1&&(c=c.slice(0,h))}return c+e},Pr.unescape=function(t){return(t=ba(t))&&J.test(t)?t.replace(H,dr):t},Pr.uniqueId=function(t){var n=++Dt;return ba(t)+n},Pr.upperCase=Ja,Pr.upperFirst=Ya,Pr.each=bo,Pr.eachRight=mo,Pr.first=Gi,cf(Pr,(Bf={},we(Pr,(function(t,n){Lt.call(Pr.prototype,n)||(Bf[n]=t)})),Bf),{chain:!1}),Pr.VERSION="4.17.21",Rn(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(t){Pr[t].placeholder=Pr})),Rn(["drop","take"],(function(t,n){Zr.prototype[t]=function(r){r=r===u?1:br(va(r),0);var e=this.__filtered__&&!n?new Zr(this):this.clone();return e.__filtered__?e.__takeCount__=mr(r,e.__takeCount__):e.__views__.push({size:mr(r,g),type:t+(e.__dir__<0?"Right":"")}),e},Zr.prototype[t+"Right"]=function(n){return this.reverse()[t](n).reverse()}})),Rn(["filter","map","takeWhile"],(function(t,n){var r=n+1,e=1==r||3==r;Zr.prototype[t]=function(t){var n=this.clone();return n.__iteratees__.push({iteratee:ci(t,3),type:r}),n.__filtered__=n.__filtered__||e,n}})),Rn(["head","last"],(function(t,n){var r="take"+(n?"Right":"");Zr.prototype[t]=function(){return this[r](1).value()[0]}})),Rn(["initial","tail"],(function(t,n){var r="drop"+(n?"":"Right");Zr.prototype[t]=function(){return this.__filtered__?new Zr(this):this[r](1)}})),Zr.prototype.compact=function(){return this.filter(uf)},Zr.prototype.find=function(t){return this.filter(t).head()},Zr.prototype.findLast=function(t){return this.reverse().find(t)},Zr.prototype.invokeMap=Ye((function(t,n){return"function"==typeof t?new Zr(this):this.map((function(r){return Ce(r,t,n)}))})),Zr.prototype.reject=function(t){return this.filter(Lo(ci(t)))},Zr.prototype.slice=function(t,n){t=va(t);var r=this;return r.__filtered__&&(t>0||n<0)?new Zr(r):(t<0?r=r.takeRight(-t):t&&(r=r.drop(t)),n!==u&&(r=(n=va(n))<0?r.dropRight(-n):r.take(n-t)),r)},Zr.prototype.takeRightWhile=function(t){return this.reverse().takeWhile(t).reverse()},Zr.prototype.toArray=function(){return this.take(g)},we(Zr.prototype,(function(t,n){var r=/^(?:filter|find|map|reject)|While$/.test(n),e=/^(?:head|last)$/.test(n),i=Pr[e?"take"+("last"==n?"Right":""):n],o=e||/^find/.test(n);i&&(Pr.prototype[n]=function(){var n=this.__wrapped__,a=e?[1]:arguments,f=n instanceof Zr,c=a[0],l=f||Zo(n),s=function(t){var n=i.apply(Pr,Dn([t],a));return e&&_?n[0]:n};l&&r&&"function"==typeof c&&1!=c.length&&(f=l=!1);var _=this.__chain__,h=!!this.__actions__.length,p=o&&!_,v=f&&!h;if(!o&&l){n=v?n:new Zr(this);var g=t.apply(n,a);return g.__actions__.push({func:ho,args:[s],thisArg:u}),new qr(g,_)}return p&&v?t.apply(this,a):(g=this.thru(s),p?e?g.value()[0]:g.value():g)})})),Rn(["pop","push","shift","sort","splice","unshift"],(function(t){var n=Ct[t],r=/^(?:push|sort|unshift)$/.test(t)?"tap":"thru",e=/^(?:pop|shift)$/.test(t);Pr.prototype[t]=function(){var t=arguments;if(e&&!this.__chain__){var u=this.value();return n.apply(Zo(u)?u:[],t)}return this[r]((function(r){return n.apply(Zo(r)?r:[],t)}))}})),we(Zr.prototype,(function(t,n){var r=Pr[n];if(r){var e=r.name+"";Lt.call(Ir,e)||(Ir[e]=[]),Ir[e].push({name:n,func:r})}})),Ir[Pu(u,2).name]=[{name:"wrapper",func:u}],Zr.prototype.clone=function(){var t=new Zr(this.__wrapped__);return t.__actions__=Ru(this.__actions__),t.__dir__=this.__dir__,t.__filtered__=this.__filtered__,t.__iteratees__=Ru(this.__iteratees__),t.__takeCount__=this.__takeCount__,t.__views__=Ru(this.__views__),t},Zr.prototype.reverse=function(){if(this.__filtered__){var t=new Zr(this);t.__dir__=-1,t.__filtered__=!0}else(t=this.clone()).__dir__*=-1;return t},Zr.prototype.value=function(){var t=this.__wrapped__.value(),n=this.__dir__,r=Zo(t),e=n<0,u=r?t.length:0,i=function(t,n,r){var e=-1,u=r.length;for(;++e=this.__values__.length;return{done:t,value:t?u:this.__values__[this.__index__++]}},Pr.prototype.plant=function(t){for(var n,r=this;r instanceof Nr;){var e=Wi(r);e.__index__=0,e.__values__=u,n?i.__wrapped__=e:n=e;var i=e;r=r.__wrapped__}return i.__wrapped__=t,n},Pr.prototype.reverse=function(){var t=this.__wrapped__;if(t instanceof Zr){var n=t;return this.__actions__.length&&(n=new Zr(this)),(n=n.reverse()).__actions__.push({func:ho,args:[to],thisArg:u}),new qr(n,this.__chain__)}return this.thru(to)},Pr.prototype.toJSON=Pr.prototype.valueOf=Pr.prototype.value=function(){return vu(this.__wrapped__,this.__actions__)},Pr.prototype.first=Pr.prototype.head,Qt&&(Pr.prototype[Qt]=function(){return this}),Pr}();gn._=yr,(e=function(){return yr}.call(n,r,n,t))===u||(t.exports=e)}.call(this)}},n={};function r(e){var u=n[e];if(void 0!==u)return u.exports;var i=n[e]={id:e,loaded:!1,exports:{}};return t[e].call(i.exports,i,i.exports,r),i.loaded=!0,i.exports}r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),r.nmd=t=>(t.paths=[],t.children||(t.children=[]),t);var e=r(825);freeaps_profile=e})(); \ No newline at end of file diff --git a/FreeAPS/Resources/javascript/prepare/determine-basal.js b/FreeAPS/Resources/javascript/prepare/determine-basal.js index 6c4fc4663c..ddd9f04aa5 100644 --- a/FreeAPS/Resources/javascript/prepare/determine-basal.js +++ b/FreeAPS/Resources/javascript/prepare/determine-basal.js @@ -28,9 +28,9 @@ function generate(iob, currenttemp, glucose, profile, autosens = null, meal = nu // ISF and CR if (dynamicVariables.isfAndCr) { profile.sense /= factor; - profile.carb_ratio /= factor; + profile.carb_ratio = round(profile.carb_ratio / factor, 1); } else { - if (dynamicVariables.cr) { profile.carb_ratio /= factor; } + if (dynamicVariables.cr) { profile.carb_ratio = round(profile.carb_ratio / factor, 1); } if (dynamicVariables.isf) { profile.sens /= factor; } } console.log("Override Active, " + dynamicVariables.overridePercentage + "%"); @@ -99,6 +99,7 @@ function generate(iob, currenttemp, glucose, profile, autosens = null, meal = nu function dynisf(profile, autosens_data, dynamicVariables, glucose) { console.log("Starting dynamic ISF layer."); var dynISFenabled = true; + // One of two exercise settings (they share the same purpose). var exerciseSetting = false; if (profile.highTemptargetRaisesSensitivity || profile.exerciseMode || dynamicVariables.isEnabled) { diff --git a/FreeAPS/Resources/javascript/prepare/middleware.js b/FreeAPS/Resources/javascript/prepare/middleware.js index 20c5da89a3..21851fef05 100644 --- a/FreeAPS/Resources/javascript/prepare/middleware.js +++ b/FreeAPS/Resources/javascript/prepare/middleware.js @@ -12,14 +12,19 @@ function generate(iob, currenttemp, glucose, profile, autosens = null, meal = nu if (profile.tddAdjBasal && dynamicVariables.average_total_data != 0) { profile.tdd_factor = Math.round( (dynamicVariables.weightedAverage / dynamicVariables.average_total_data) * 100) / 100; - const adjusted = Math.min(Math.max(profile.autosens_min, profile.tdd_factor), profile.autosens_max); - if (profile.tdd_factor != adjusted) { console.log("Dynamic basal adjustment limited by your autosens_min/max settings to: " + adjusted); profile.tdd_factor = adjusted; } } + + if (profile.out_units == 'mmol/L') { + profile.old_isf = Math.round(profile.sens * 0.0555 * 10) / 10; + } else { + profile.old_isf = profile.sens; + } + profile.old_cr = profile.carb_ratio; if (profile.useNewFormula && profile.temptargetSet && (profile.high_temptarget_raises_sensitivity || profile.exercise_mode || dynamicVariables.isEnabled) && profile.min_bg >= 118) { profile.useNewFormula = false; diff --git a/FreeAPS/Resources/javascript/prepare/profile.js b/FreeAPS/Resources/javascript/prepare/profile.js index 572792dc0b..fd3f6e4b99 100644 --- a/FreeAPS/Resources/javascript/prepare/profile.js +++ b/FreeAPS/Resources/javascript/prepare/profile.js @@ -84,7 +84,9 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data var tdd_factor = { }; var set_basal = false; var basal_rate = { }; - + var old_isf = { }; + var old_cr = { }; + var inputs = { }; //add all preferences to the inputs for (var pref in preferences) { @@ -112,6 +114,9 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data inputs.tddFactor = tdd_factor; inputs.set_basal = set_basal; inputs.basal_rate = basal_rate; + inputs.old_isf = old_isf; + inputs.old_cr = old_cr; + if (autotune_data) { if (autotune_data.basalprofile) { inputs.basals = autotune_data.basalprofile; } @@ -120,5 +125,42 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data if (autotune_data.carb_ratio) { inputs.carbratio.schedule[0].ratio = autotune_data.carb_ratio; } } } - return freeaps_profile(inputs); + + // merge oref0 defaults with iAPS ones + const defaults = Object.assign( + {}, + freeaps_profile.defaults(), + { + type: 'iAPS', // attribute to override defaults + // +++++ iAPS settings + // smb_delivery_ratio: included in the current oref0 PR (https://github.com/openaps/oref0/pull/1465/files) + smb_delivery_ratio: 0.5, + adjustmentFactor: 1, + useNewFormula: false, + enableDynamicCR: false, + sigmoid: false, + weightPercentage: 0.65, + tddAdjBasal: false, + // threshold_setting: temporary fix to test thomasvargiu/iAPS#original-oref0 branch before build. + // We can remove it after merged and after build the new original bundles + // because it's included in the current oref0 PR (https://github.com/openaps/oref0/pull/1465/files) + // currently (2024-08-09) this settings probably doesn't work in the current iAPS main/dev branch + threshold_setting: 60 + } + ) + + var logs = { err: '', stdout: '', return_val: 0 }; + var profile = freeaps_profile(logs, inputs, defaults); + if (logs.err.length > 0) { + console.error(logs.err); + } + if (logs.stdout.length > 0) { + console.error(logs.stdout); + } + + if (typeof profile !== 'object') { + return; + } + + return profile; } diff --git a/FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json b/FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json index b3e135569a..55ed5f35b5 100644 --- a/FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json +++ b/FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json @@ -40,7 +40,7 @@ "yGridLines" : true, "oneDimensionalGraph" : false, "rulerMarks" : false, - "maxCarbs" : 1000, + "maxCarbs" : 200, "displayFatAndProteinOnWatch" : false, "confirmBolusFaster" : false, "onlyAutotuneBasals" : false, @@ -62,5 +62,6 @@ "minimumSMB": 0.3, "useInsulinBars": false, "uploadVersion": true, - "birthDate": Date.distantPast + "birthDate": Date.distantPast, + "displayDelta": false } diff --git a/FreeAPS/Sources/APS/APSManager.swift b/FreeAPS/Sources/APS/APSManager.swift index 23cd284681..6fc27ad5b7 100644 --- a/FreeAPS/Sources/APS/APSManager.swift +++ b/FreeAPS/Sources/APS/APSManager.swift @@ -51,7 +51,7 @@ enum APSError: LocalizedError { case let .invalidPumpState(message): return "Error: Invalid Pump State: \(message)" case let .bolusInProgress(message): - return "\(NSLocalizedString("Error: Pump is Busy.", comment: "Pump Error")) \(NSLocalizedString(message, comment: "Pump Error Message"))" + return "\(NSLocalizedString("Pump is Busy.", comment: "Pump Error")) \(NSLocalizedString(message, comment: "Pump Error Message"))" case let .glucoseError(message): return "Error: Invalid glucose: \(message)" case let .apsError(message): @@ -829,7 +829,8 @@ final class BaseAPSManager: APSManager, Injectable { let saveLastLoop = LastLoop(context: self.coredataContext) saveLastLoop.iob = (enacted.iob ?? 0) as NSDecimalNumber saveLastLoop.cob = (enacted.cob ?? 0) as NSDecimalNumber - saveLastLoop.timestamp = (enacted.timestamp ?? .distantPast) as Date + saveLastLoop.timestamp = received ? enacted.timestamp : CoreDataStorage().fetchLastLoop()? + .timestamp ?? .distantPast try? self.coredataContext.save() } diff --git a/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift b/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift index 3747862418..d1d006d56c 100644 --- a/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift +++ b/FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift @@ -21,6 +21,10 @@ final class OpenAPS { func determineBasal(currentTemp: TempBasal, clock: Date = Date()) -> Future { Future { promise in self.processQueue.async { + let start = Date.now + + var now = Date.now + debug(.openAPS, "Start determineBasal") // clock self.storage.save(clock, as: Monitor.clock) @@ -36,7 +40,14 @@ final class OpenAPS { // To do: remove this struct. let dynamicVariables = self.loadFileFromStorage(name: Monitor.dynamicVariables) - var now = Date.now + let tdd = CoreDataStorage().fetchInsulinDistribution().first + + print( + "Time for Loading files \(-1 * now.timeIntervalSinceNow) seconds" + ) + + now = Date.now + let meal = self.meal( pumphistory: pumpHistory, profile: profile, @@ -45,7 +56,9 @@ final class OpenAPS { carbs: carbs, glucose: glucose ) - print("Time for Determine Basal: step after meal module \(-1 * now.timeIntervalSinceNow) seconds") + print( + "Time for Meal module \(-1 * now.timeIntervalSinceNow) seconds, total: \(-1 * start.timeIntervalSinceNow)" + ) self.storage.save(meal, as: Monitor.meal) @@ -59,7 +72,9 @@ final class OpenAPS { autosens: autosens.isEmpty ? .null : autosens ) self.storage.save(iob, as: Monitor.iob) - print("Time for Determine Basal: step after IOB module \(-1 * now.timeIntervalSinceNow) seconds") + print( + "Time for IOB module \(-1 * now.timeIntervalSinceNow) seconds, total: \(-1 * start.timeIntervalSinceNow)" + ) // determine-basal let reservoir = self.loadFileFromStorage(name: Monitor.reservoir) @@ -77,6 +92,7 @@ final class OpenAPS { dynamicVariables: dynamicVariables ) + now = Date.now // The OpenAPS JS algorithm layer let suggested = self.determineBasal( glucose: glucose, @@ -90,10 +106,15 @@ final class OpenAPS { dynamicVariables: dynamicVariables ) + print( + "Time for Determine Basal module \(-1 * now.timeIntervalSinceNow) seconds, total: \(-1 * start.timeIntervalSinceNow)" + ) + debug(.openAPS, "SUGGESTED: \(suggested)") // Update Suggestion if var suggestion = Suggestion(from: suggested) { + now = Date.now // Process any eventual middleware basal rate if let newSuggestion = self.overrideBasal(alteredProfile: alteredProfile, oref0Suggestion: suggestion) { suggestion = newSuggestion @@ -103,13 +124,18 @@ final class OpenAPS { reason: suggestion.reason, suggestion: suggestion, preferences: preferencesData, - profile: alteredProfile + profile: alteredProfile, + tdd: tdd ) // Update time suggestion.timestamp = suggestion.deliverAt ?? clock // Save self.storage.save(suggestion, as: Enact.suggested) + print( + "Time for updating and saving reasons: \(-1 * now.timeIntervalSinceNow) seconds, total: \(-1 * start.timeIntervalSinceNow)" + ) + promise(.success(suggestion)) } else { promise(.success(nil)) @@ -209,7 +235,7 @@ final class OpenAPS { let freeaps = self.loadFileFromStorage(name: FreeAPS.settings) let preferencesData = Preferences(from: preferences) let tdd = self.tdd(preferencesData: preferencesData) - if let insulin = tdd, (insulin.basal + insulin.bolus) > 0 { + if let insulin = tdd, insulin.hours > 0 { CoreDataStorage().saveTDD(insulin) } let dynamicVariables = self.dynamicVariables(preferencesData) @@ -260,21 +286,24 @@ final class OpenAPS { private func reasons( reason: String, suggestion: Suggestion, - preferences: Preferences?, - profile: RawJSON + preferences _: Preferences?, + profile: RawJSON, + tdd: InsulinDistribution? ) -> String { var reasonString = reason let startIndex = reasonString.startIndex - let tdd = tdd(preferencesData: preferences) // Autosens.ratio / Dynamic Ratios if let isf = suggestion.sensitivityRatio { // TDD var tddString = "" - if let total = tdd { - let round = round(Double((total.bolus + total.basal) * 10)) / 10 - let bolus = Int(total.bolus * 100 / ((total.bolus + total.basal) != 0 ? total.bolus + total.basal : 1)) - tddString = ", TDD: \(round) U, \(bolus) % Bolus, " + if let tdd = tdd { + let total = ((tdd.bolus ?? 0) as Decimal) + ((tdd.tempBasal ?? 0) as Decimal) + let round = round(Double(total * 10)) / 10 + + let bolus = Int(((tdd.bolus ?? 0) as Decimal) * 100 / (total != 0 ? total : 1)) + + tddString = ", Insulin 24h: \(round) U, \(bolus) % Bolus, " } else { tddString = ", " } @@ -292,9 +321,9 @@ final class OpenAPS { insertedResons += ", AF: \(value)" } if let dynamicCR = readJSON(json: profile, variable: "enableDynamicCR"), Bool(dynamicCR) ?? false { - insertedResons += ", Dynamic ISF/CR: On/On" + insertedResons += ", Dynamic ISF/CR is: On/On" } else { - insertedResons += ", Dynamic ISF/CR: On/Off" + insertedResons += ", Dynamic ISF/CR is: On/Off" } if let tddFactor = readMiddleware(json: profile, variable: "tdd_factor"), tddFactor.count > 1 { insertedResons += ", Basal Adjustment: \(tddFactor.suffix(max(tddFactor.count - 6, 0)))" @@ -306,6 +335,20 @@ final class OpenAPS { } else { reasonString.insert(contentsOf: "Autosens Ratio: \(isf)" + tddString, at: startIndex) } + + // Include ISF before eventual adjustment + if let old = readMiddleware(json: profile, variable: "old_isf"), + let new = readReason(reason: reason, variable: "ISF"), let oldISF = trimmedIsEqual(string: old, decimal: new) + { + reasonString = reasonString.replacingOccurrences(of: "ISF:", with: "ISF: \(oldISF) →") + } + + // Include CR before eventual adjustment + if let old = readMiddleware(json: profile, variable: "old_cr"), + let new = readReason(reason: reason, variable: "CR"), let oldCR = trimmedIsEqual(string: old, decimal: new) + { + reasonString = reasonString.replacingOccurrences(of: "CR:", with: "CR: \(oldCR) →") + } } // Display either Target or Override (where target is included). @@ -365,6 +408,7 @@ final class OpenAPS { saveSuggestion.cob = cob as NSDecimalNumber saveSuggestion.target = target as NSDecimalNumber saveSuggestion.minPredBG = minPredBG as NSDecimalNumber + saveSuggestion.eventualBG = Decimal(suggestion.eventualBG ?? 100) as NSDecimalNumber saveSuggestion.date = Date.now try? coredataContext.save() @@ -376,6 +420,16 @@ final class OpenAPS { return reasonString } + private func trimmedIsEqual(string: String, decimal: Decimal) -> String? { + let old = string.replacingOccurrences(of: ": ", with: "").replacingOccurrences(of: "f", with: "") + let new = "\(decimal)" + + guard old != new else { + return nil + } + return old + } + private func overrideBasal(alteredProfile: RawJSON, oref0Suggestion: Suggestion) -> Suggestion? { guard let changeRate = readJSON(json: alteredProfile, variable: "set_basal"), Bool(changeRate) ?? false, let basal_rate_is = readJSON(json: alteredProfile, variable: "basal_rate") else { return nil } diff --git a/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift b/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift index 9877adc0a2..fe564b3e90 100644 --- a/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift +++ b/FreeAPS/Sources/APS/Storage/CoreDataStorage.swift @@ -334,4 +334,16 @@ final class CoreDataStorage { } return presetsArray.first?.name ?? "default" } + + func fetchLastLoop() -> LastLoop? { + var lastLoop = [LastLoop]() + coredataContext.performAndWait { + let requestLastLoop = LastLoop.fetchRequest() as NSFetchRequest + let sortLoops = NSSortDescriptor(key: "timestamp", ascending: false) + requestLastLoop.sortDescriptors = [sortLoops] + requestLastLoop.fetchLimit = 1 + try? lastLoop = coredataContext.fetch(requestLastLoop) + } + return lastLoop.first + } } diff --git a/FreeAPS/Sources/APS/Storage/OverrideStorage.swift b/FreeAPS/Sources/APS/Storage/OverrideStorage.swift index badd89a15c..4da0393356 100644 --- a/FreeAPS/Sources/APS/Storage/OverrideStorage.swift +++ b/FreeAPS/Sources/APS/Storage/OverrideStorage.swift @@ -100,6 +100,7 @@ final class OverrideStorage { save.smbIsOff = preset.smbIsOff save.smbMinutes = preset.smbMinutes save.uamMinutes = preset.uamMinutes + save.maxIOB = preset.maxIOB save.target = preset.target try? coredataContext.save() } diff --git a/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings index 2de9eb192d..a6a118eade 100644 --- a/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings @@ -4,242 +4,246 @@ */ /* -------------------------------- */ /* Bolus screen when adding insulin */ -"Add insulin without actually bolusing" = "Add insulin without actually bolusing"; +"Add insulin without actually bolusing" = "إضافة الأنسولين دون تنفيذ الجرعة"; /* Add insulin from source outside of pump */ -"Add %@ without bolusing" = "Add %@ without bolusing"; +"Add %@ without bolusing" = "إضافة %@ دون تنفيذ الجرعة"; -"Bolus" = "Bolus"; +"Bolus" = "الجرعة"; -"Close" = "Close"; +"Close" = "إغلاق"; /* Continue after added carbs without bolus */ -"Continue without bolus" = "Continue without bolus"; +"Continue without bolus" = "متابعة دون ضخ جرعة"; /* Continue after added meal without bolus */ -"Save Meal without bolus" = "Save Meal without bolus"; +"Save Meal without bolus" = "إضافة وجبة دون ضخ جرعة"; /* Predictions and Meal summary part of the Bolus View. */ "Status" = "الحالة"; /* Alert when adding large amount without bolusing */ -"\nAmount is more than your Max Bolus setting! \nAre you sure you want to add " = "\nAmount is more than your Max Bolus setting! \nAre you sure you want to add "; +"\nAmount is more than your Max Bolus setting! \nAre you sure you want to add " = "\nالكمية أكثر من الحد الأقصى المحدد للجرعة! +\nهل أنت متيقِّن أنك تريد إضافة "; /* Header */ -"Enact Bolus" = "Enact Bolus"; +"Enact Bolus" = "ضخ الجرعة"; /* Button */ -"Enact bolus" = "Enact bolus"; +"Enact bolus" = "ضخ الجرعة"; /* */ -"Insulin recommended" = "Insulin recommended"; +"Insulin recommended" = "الإنسولين الموصى به"; /* */ -"Insulin required" = "Insulin required"; +"Insulin required" = "الإنسولين المطلوب"; /* Bolus screen */ -"Recommendation" = "Recommendation"; +"Recommendation" = "المقترحات"; /* Button */ -"Clear" = "Clear"; +"Clear" = "مسح"; /* Button */ -"Done" = "Done"; +"Done" = "تمّ"; /* Bolus View footer */ -"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run"; +"Last loop %@ minutes ago. Complete or cancel this meal/bolus transaction to allow for next loop cycle to run" = "آخر دورة قبل %@ دقيقة. أكمل أو ألغِ هذه الوجبة/الجرعة للسماح بتشغيل الدورة التالية"; /* Calender Option */ -"Display Emojis as Labels" = "Display Emojis as Labels"; +"Display Emojis as Labels" = "عرض الرموز التعبيرية كتسميات"; /* Calender Option */ -"Display IOB and COB" = "Display IOB and COB"; +"Display IOB and COB" = "عرض IOB و COB"; /* Apple Watch App setting */ -"Confirm Bolus Faster" = "Confirm Bolus Faster"; +"Confirm Bolus Faster" = "تفعيل خاصية تأكيد البولوس بشكل اسرع"; /* Setting Section */ -"UI/UX" = "UI/UX"; +"UI/UX" = "اعدادات واجهة/تجربة المستخدم"; /* */ -"Wait please" = "Wait please"; +"Wait please" = "انتظر من فضلك"; /* */ -"Agree and continue" = "Agree and Continue"; +"Agree and continue" = "الموافقة والمتابعة"; /* */ -"Log external insulin" = "Log external insulin"; +"Log external insulin" = "سجل الأنسولين الخارجي"; /* Bolus progress view */ -"of" = "of"; +"of" = "من"; /* Remote Bolus Alert, Part 1 */ -"A Remote Bolus " = "A Remote Bolus "; +"A Remote Bolus " = "جرعة عن بعد "; /* Remote Bolus Alert, Part 2 */ -"was delivered" = "was delivered"; +"was delivered" = "تم تسليمها"; /* Remote Bolus Alert, Part 3 */ " minutes ago, triggered remotely from Nightscout, by a caregiver or a parent. Do you still want to bolus?\n\nPredicted eventual glucose, if you don't bolus, is: " = " minutes ago, triggered remotely from Nightscout, by a caregiver or a parent. Do you still want to bolus?\n\nPredicted eventual glucose, if you don't bolus, is: "; /* Remote Bolus Alert, Title */ -"A Remote Bolus Was Just Delivered!" = "A Remote Bolus Was Just Delivered!"; +"A Remote Bolus Was Just Delivered!" = "تم تسليم الجرعة عن بعد للتو!"; /* Headline in enacted pop up (at: at what time) */ -"Enacted at" = "Enacted at"; +"Enacted at" = "تم تفيعله"; /* Headline in suggested pop up (at: at what time) */ -"Suggested at" = "Suggested at"; +"Suggested at" = "تم اقتراحه"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error at"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ -"Meal Summary" = "Meal Summary"; +"Meal Summary" = "موجز الوجبة"; /* Bolus View Meal Edit Meal Button */ -"Edit Meal" = "Edit Meal"; +"Edit Meal" = "تعديل الوجبة"; /* Bolus View Meal Add Meal Button */ -"Add Meal" = "Add Meal"; +"Add Meal" = "إضافة وجبة"; /* Bolus View Bolus Summary Header */ -"Bolus Summary" = "Bolus Summary"; +"Bolus Summary" = "موجز الجرعات"; /* For the Bolus View pop-up */ -"Calculations" = "Calculations"; +"Calculations" = "حساب"; /* For the Bolus View pop-up */ -"Fatty Meal" = "Fatty Meal"; +"Fatty Meal" = "وجبة دهنية"; + +/* Bolus option */ +"Hypo Treatment" = "علاج إنخفاض"; /* For the Bolus View pop-up */ -"Full Bolus" = "Full Bolus"; +"Full Bolus" = "جرعة كاملة"; /* For the Bolus View pop-up */ -"Fraction" = "Fraction"; +"Fraction" = "جزء"; /* For the Bolus View pop-up */ -"Fatty Meal Factor" = "Fatty Meal Factor"; +"Fatty Meal Factor" = "عامل الوجبة الدهنية"; /* Footer */ -"Carbs and previous insulin are included in the eventual glucose prediction." = "Carbs and previous insulin are included in the eventual glucose prediction."; +"Carbs and previous insulin are included in the eventual glucose prediction." = "الكربونات والأنسولين السابقة مدرجة في التنبؤ بالجلوكوز في نهاية المطاف."; /* For the Bolus View pop-up */ -"Result" = "Result"; +"Result" = "النتيجة"; /* For the Bolus View pop-up */ -"Your entered amount was limited by your max Bolus setting of %d%@" = "Your entered amount was limited by your max Bolus setting of %d%@"; +"Your entered amount was limited by your max Bolus setting of %d%@" = "تم تحديد المبلغ الذي أدخلته من خلال إعدادتك القصوى Bols %d%@"; /* Bolus View Continue Button */ "Continue" = "Continue"; /* Home title */ -"Home" = "Home"; +"Home" = "الرئيسية"; /* Looping in progress */ "looping" = "looping"; /* min ago since last loop */ -"min ago" = "min ago"; +"min ago" = "قبل دقيقة"; /* Status Title */ -"No suggestion" = "No suggestion"; +"No suggestion" = "لا اقتراحات"; /* Replace pod text in Header */ -"Replace pod" = "Replace pod"; +"Replace pod" = "استبدال البود"; /* Add carbs screen */ -"Add Carbs" = "Add Carbs"; +"Add Carbs" = "إضافة كربونات"; /* Add carbs header and button in Watch app. You can skip the last " " space. It's just for differentiation */ -"Add Carbs " = "Add Carbs "; +"Add Carbs " = "إضافة كربونات "; /* */ -"Amount Carbs" = "Amount Carbs"; +"Amount Carbs" = "كمية الكاربوهيدرات"; /* Grams unit */ -"grams" = "grams"; +"grams" = "جرام"; /* */ -"Carbs required" = "Carbs required"; +"Carbs required" = "الكاربوهيدرات المطلوبة"; /* Saved Food Presets */ -"Saved Food" = "Saved Food"; +"Saved Food" = "الوجبات المحفوظة"; /* */ -"Are you sure?" = "Are you sure?"; +"Are you sure?" = "هل أنت متأكد؟"; /* Bottom target temp */ -"Bottom target" = "Bottom target"; +"Bottom target" = "الهدف السفلي"; /* Cancel preset name */ -"Cancel" = "Cancel"; +"Cancel" = "إلغاء"; /* */ -"Cancel Temp Target" = "Cancel Temp Target"; +"Cancel Temp Target" = "إلغاء الهدف المؤقت"; /* Custom temp target */ -"Custom" = "Custom"; +"Custom" = "مخصص"; /* */ -"Date" = "Date"; +"Date" = "التاريخ"; /* */ -"Delete" = "Delete"; +"Delete" = "حذف"; /* Delete preset temp target */ -"Delete preset \"%@\"" = "Delete preset \"%@\""; +"Delete preset \"%@\"" = "حذف الإعداد المسبق \"%@\""; /* Duration of target temp or temp basal */ -"Duration" = "Duration"; +"Duration" = "المدة"; /* */ -"Enact Temp Target" = "Enact Temp Target"; +"Enact Temp Target" = "تفعيل الهدف المؤقت"; /* */ -"Target" = "Target"; +"Target" = "الهدف"; /* */ -"Basal Insulin and Sensitivity ratio" = "Basal Insulin and Sensitivity ratio"; +"Basal Insulin and Sensitivity ratio" = "نسبة الإنسولين الأساسي والحساسية"; /* */ -"A lower 'Half Basal Target' setting will reduce the basal and raise the ISF earlier, at a lower target glucose." = "A lower 'Half Basal Target' setting will reduce the basal and raise the ISF earlier, at a lower target glucose."; +"A lower 'Half Basal Target' setting will reduce the basal and raise the ISF earlier, at a lower target glucose." = "إعداد 'نصف الهدف الأساسي' سوف يقلل من القاعدي و يرفع من (حساسية الانسولين) في وقت أسبق، عند مستوى جلكوز مستهدف أقل."; /* */ -" Your setting: " = " Your setting: "; +" Your setting: " = " الإعدادات الخاصة بك: "; /* */ "mg/dl. Autosens.max limits the max endpoint" = "mg/dl. Autosens.max limits the max endpoint"; /* */ -"Enter preset name" = "Enter preset name"; +"Enter preset name" = "أدخل اسم التعيين المسبق"; /* Preset name */ -"Name" = "Name"; +"Name" = "الاسم"; /* minutes of target temp */ -"minutes" = "minutes"; +"minutes" = "دقائق"; /* */ -"Presets" = "Presets"; +"Presets" = "التعيينات المسبقة"; /* Save preset name */ -"Save" = "Save"; +"Save" = "إحفظ"; /* */ -"Save as Preset" = "Save as Preset"; +"Save as Preset" = "حفظ كتعيين مسبق"; /* Delete Meal Preset */ -"Delete Preset" = "Delete Preset"; +"Delete Preset" = "حذف التعيين المسبق"; /* Confirm Deletion */ -"Delete preset '%@'?" = "Delete preset '%@'?"; +"Delete preset '%@'?" = "حذف التعيين المسبق '%@'؟"; /* Button */ -"No" = "No"; +"No" = "لا"; /* Button */ -"Yes" = "Yes"; +"Yes" = "نعم"; /* + Button */ "[ +1 ]" = "[ +1 ]"; @@ -248,119 +252,119 @@ "[ -1 ]" = "[ -1 ]"; /* Upper temp target limit */ -"Top target" = "Top target"; +"Top target" = "الحد الاعلى من الهدف"; /* Temp target set for ... minutes */ -"for" = "for"; +"for" = "لـ"; /* Temp target set for ... minutes */ -"min" = "min"; +"min" = "د"; /* */ -"Autotune" = "Autotune"; +"Autotune" = "ضبط الانسولين التلقائي"; /* */ -"Basal profile" = "Basal profile"; +"Basal profile" = "ملف الانسولين القاعدي"; /* */ -"Carb ratio" = "Carb ratio"; +"Carb ratio" = "معامل الكارب"; /* */ -"Delete autotune data" = "Delete autotune data"; +"Delete autotune data" = "حذف بيانات ضبط الانسولين التلقائي"; /* */ -"Run now" = "Run now"; +"Run now" = "نفذ الأن"; /* */ -"Last run" = "Last run"; +"Last run" = "آخر تنفيذ"; /* */ -"Sensitivity" = "Sensitivity"; +"Sensitivity" = "حساسية"; /* */ "Use Autotune" = "use Autotune"; /* Add profile basal */ -"Add" = "Add"; +"Add" = "أضف"; /* */ -"Basal Profile" = "Basal Profile"; +"Basal Profile" = "ملف الانسولين القاعدي"; /* Rate basal profile */ -"Rate" = "Rate"; +"Rate" = "معدّل"; /* */ -"Save on Pump" = "Save on Pump"; +"Save on Pump" = "الحفظ في المضخة"; /* */ -"Schedule" = "Schedule"; +"Schedule" = "الجدول"; /* */ -"starts at" = "starts at"; +"starts at" = "يبدأ عند"; /* Time basal profile */ -"Time" = "Time"; +"Time" = "الوقت"; /* */ -"Calculated Ratio" = "Calculated Ratio"; +"Calculated Ratio" = "النسبة المحسوبة"; /* Carb Ratios header */ -"Carb Ratios" = "Carb Ratios"; +"Carb Ratios" = "معاملات الكارب"; /* */ -"Ratio" = "Ratio"; +"Ratio" = "النسبة"; /* */ -"Autosens" = "Autosens"; +"Autosens" = "حساسية الانسولين التلقائية"; /* */ -"Calculated Sensitivity" = "Calculated Sensitivity"; +"Calculated Sensitivity" = "الحساسية المحسوبة"; /* */ -"Insulin Sensitivities" = "Insulin Sensitivities"; +"Insulin Sensitivities" = "حساسية الأنسولين"; /* */ -"Sensitivity Ratio" = "Sensitivity Ratio"; +"Sensitivity Ratio" = "معدل الحساسية"; /* */ -"Dismiss" = "Dismiss"; +"Dismiss" = "تجاهل"; /* */ -"Important message" = "Important message"; +"Important message" = "رسالة مهمة"; /* */ -"Amount" = "Amount"; +"Amount" = "الكمية"; /* */ -"Cancel Temp Basal" = "Cancel Temp Basal"; +"Cancel Temp Basal" = "إلغاء الضخ المؤقت للإنسولين القاعدي"; /* Enact Enact a temp Basal or a temp target */ -"Enact" = "Enact"; +"Enact" = "فعل"; /* Start a temp target or a profile override */ -"Start" = "Start"; +"Start" = "إبدأ"; /* */ -"Manual Temp Basal" = "Manual Temp Basal"; +"Manual Temp Basal" = "إنسولين قاعدي يدوي مؤقت"; /* Allow uploads to different services */ -"Allow uploads" = "Allow uploads"; +"Allow uploads" = "السماح بالتحميل"; /* API secret in NS */ -"API secret" = "API secret"; +"API secret" = "سر API"; /* Connect to NS */ -"Connect" = "Connect"; +"Connect" = "توصيل"; /* Connected to NS */ -"Connected!" = "Connected!"; +"Connected!" = "متصل!"; /* Connecting to NS */ -"Connecting..." = "Connecting..."; +"Connecting..." = "الاتصال جارِ..."; /* */ -"Invalid URL" = "Invalid URL"; +"Invalid URL" = "عنوان URL غير صحيح"; /* */ "Local glucose source" = "Local glucose source"; @@ -372,7 +376,7 @@ Enact a temp Basal or a temp target */ "Port" = "Port"; /* */ -"URL" = "URL"; +"URL" = "عنوان الرابط"; /**/ "Use local glucose server" = "Use local glucose server"; @@ -381,7 +385,7 @@ Enact a temp Basal or a temp target */ "This enables uploading of statistics.json to Nightscout, which can be used by the Community Statistics and Demographics Project.\n\nParticipation in Community Statistics is opt-in, and requires separate registration at:\n" = "This enables uploading of statistics.json to Nightscout, which can be used by the Community Statistics and Demographics Project.\n\nParticipation in Community Statistics is opt-in, and requires separate registration at:\n"; /* */ -"Edit settings json" = "Edit settings json"; +"Edit settings json" = "تحرير إعدادات json"; /* */ "Glucose units" = "Glucose units"; @@ -476,6 +480,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pump Settings"; @@ -507,163 +514,163 @@ Enact a temp Basal or a temp target */ " g" = " g"; /* The short unit display string for grams */ -"g" = "g"; +"g" = "ج"; /* when 0 U/hr */ -"0 U/hr" = "0 U/hr"; +"0 U/hr" = "0 وحدة/ساعة"; /* abbreviation for days */ -"d" = "d"; +"d" = "يوم"; /* abbreviation for hours */ -"h" = "h"; +"h" = "س"; /* abbreviation for minutes */ -"m" = "m"; +"m" = "د"; /* */ -"Closed loop" = "Closed loop"; +"Closed loop" = "حلقة مغلقة"; /* */ -"Configuration" = "Configuration"; +"Configuration" = "ضبط"; /* */ -"Devices" = "Devices"; +"Devices" = "الأجهزة"; /* */ -"Pump" = "Pump"; +"Pump" = "المضخة"; /* */ -"Watch" = "Watch"; +"Watch" = "ساعه"; /* */ -"Watch Configuration" = "Watch Configuration"; +"Watch Configuration" = "اعدادات الساعة"; /* */ -"Apple Watch" = "Apple Watch"; +"Apple Watch" = "ساعة أبل"; /* */ -"Display on Watch" = "Display on Watch"; +"Display on Watch" = "عرض على الساعة"; /* */ -"Garmin Watch" = "Garmin Watch"; +"Garmin Watch" = "ساعة جارمين"; /* */ -"Add devices" = "Add devices"; +"Add devices" = "إضافة أجهزة"; /* */ -"Glucose Target" = "Glucose Target"; +"Glucose Target" = "هدف الجلوكوز"; /* */ -"Heart Rate" = "Heart Rate"; +"Heart Rate" = "معدل ضربات القلب"; /* */ -"Steps" = "Steps"; +"Steps" = "الخطوات"; /* */ -"ISF" = "ISF"; +"ISF" = "عامل حساسية الأنسولين"; /* */ "The app Garmin Connect must be installed to use for iAPS.\n Go to App Store to download it" = "The app Garmin Connect must be installed to use for iAPS.\n Go to App Store to download it"; /* */ -"Garmin is not available" = "Garmin is not available"; +"Garmin is not available" = "جارمين غير متاح"; /* */ -"Services" = "Services"; +"Services" = "الخدمات"; /* */ -"Settings" = "Settings"; +"Settings" = "الإعدادات"; /* Recommendation for a Manual Bolus */ -"Recommended Bolus Percentage" = "Recommended Bolus Percentage"; +"Recommended Bolus Percentage" = "النسبة المئوية للبولوس الموصى بها"; /* 2 log files to share */ -"Share logs" = "Share logs"; +"Share logs" = "شارك السجلات"; /* Upper target */ -"High target" = "High target"; +"High target" = "هدف مرتفع"; /* Lower target */ -"Low target" = "Low target"; +"Low target" = "هدف منخفض"; /* When bolusing */ -"Bolusing" = "Bolusing"; +"Bolusing" = "يضخ"; /* */ -"Pump suspended" = "Pump suspended"; +"Pump suspended" = "المضخة معلقة"; /* */ -"Middleware" = "Middleware"; +"Middleware" = "الوسطاء"; /* Header */ -"History" = "History"; +"History" = "السّجل"; /* Nightscout option */ -"Upload" = "Upload"; +"Upload" = "رفع"; /* Nightscout option */ -"Allow Uploads" = "Allow Uploads"; +"Allow Uploads" = "السماح بالرفع"; /* Type of CGM or glucose source */ -"Type" = "Type"; +"Type" = "النّوع"; /* CGM */ -"CGM" = "CGM"; +"CGM" = "مستشعر الجلوكوز"; /* CGM Transmitter ID */ -"Transmitter ID" = "Transmitter ID"; +"Transmitter ID" = "معرف المرسل"; /* Other CGM setting */ -"Other" = "Other"; +"Other" = "أخرى"; /* Whatch app alert */ -"Set temp targets presets on iPhone first" = "Set temp targets presets on iPhone first"; +"Set temp targets presets on iPhone first" = "تعيين أهداف المؤقتة مسبقاً على الايفون أولاً"; /* Updating Watch app */ -"Updating..." = "Updating..."; +"Updating..." = "جارٍ التحديث..."; /* Header for Temp targets in Watch app */ -"Temp Targets" = "Temp Targets"; +"Temp Targets" = "الأهداف المؤقتة"; /* Delete carbs from data table and Nightscout */ -"Delete Carbs?" = "Delete Carbs?"; +"Delete Carbs?" = "حذف النشويات؟"; /* Delete insulin from pump history and Nightscout */ -"Delete Insulin?" = "Delete Insulin?"; +"Delete Insulin?" = "حذف الإنسولين؟"; /* Treatments list */ -"Treatments" = "Treatments"; +"Treatments" = "العلاجات"; /* " min" in Treatments list */ -" min" = " min"; +" min" = " د"; /* */ -"Unable to change anything" = "Unable to change anything"; +"Unable to change anything" = "غير قادر على تغيير أي شيء"; /* Calendar and Libre transmitter settings --------------- */ /* */ -"Configure Libre Transmitter" = "Configure Libre Transmitter"; +"Configure Libre Transmitter" = "ضبط مرسل ليبري"; /* */ -"Calibrations" = "Calibrations"; +"Calibrations" = "المعايرة"; /* */ -"Create Events in Calendar" = "Create Events in Calendar"; +"Create Events in Calendar" = "إنشاء أحداث في التقويم"; /* */ -"Calendar" = "Calendar"; +"Calendar" = "التقويم"; /* Automatic delivered treatments */ -"Automatic" = "Automatic"; +"Automatic" = "تلقائي"; /* External insulin treatments */ "External" = "External"; /* */ -"Other" = "Other"; +"Other" = "أخرى"; /* */ "Libre Transmitter" = "Libre Transmitter"; @@ -810,154 +817,154 @@ Enact a temp Basal or a temp target */ "Detected sensor seems not to be a libre 1 sensor!" = "Detected sensor seems not to be a libre 1 sensor!"; /* Detected sensor is invalid: %@ */ -"Detected sensor is invalid: %@" = "Detected sensor is invalid: %@"; +"Detected sensor is invalid: %@" = "المستشعر المكتشف غير صالح: %@"; /* Low Battery */ -"Low battery" = "Low battery"; +"Low battery" = "البطارية منخفضة"; /* */ -"Invalid sensor" = "Invalid sensor"; +"Invalid sensor" = "مستشعر غير صالح"; /* */ -"Sensor change" = "Sensor change"; +"Sensor change" = "تغيير جهاز الاستشعار"; /* */ -"Sensor expires soon" = "Sensor expires soon"; +"Sensor expires soon" = "ستنتهي صلاحية المستشعر قريبا"; /* Battery is running low %@, consider charging your %@ device as soon as possible */ -"Battery is running low %@, consider charging your %@ device as soon as possible" = "Battery is running low %@, consider charging your %@ device as soon as possible"; +"Battery is running low %@, consider charging your %@ device as soon as possible" = "البطارية منخفضه %@، إشحن جهازك %@ في أقرب وقت ممكن"; /* Extracting calibrationdata from sensor */ -"Extracting calibrationdata from sensor" = "Extracting calibrationdata from sensor"; +"Extracting calibrationdata from sensor" = "جاري إستخراج بيانات المعايرة من المستشعر"; /* Sensor Ending Soon */ -"Sensor Ending Soon" = "Sensor Ending Soon"; +"Sensor Ending Soon" = "المستشعر سينتهي قريباً"; /* Current Sensor is Ending soon! Sensor Life left in %@ */ -"Current Sensor is Ending soon! Sensor Life left in %@" = "Current Sensor is Ending soon! Sensor Life left in %@"; +"Current Sensor is Ending soon! Sensor Life left in %@" = "المستشعر الحالي سينتهي قريباً! عمر المستشعر المتبقي %@"; /* */ -"Libre Bluetooth" = "Libre Bluetooth"; +"Libre Bluetooth" = "بلوتوث ليبري"; /* */ -"Snooze Alerts" = "Snooze Alerts"; +"Snooze Alerts" = "إغفاء التنبيهات"; /* */ -"Last measurement" = "Last measurement"; +"Last measurement" = "القياس الأخير"; /* */ "Sensor Footer checksum" = "Sensor Footer checksum"; /* */ -"Last Blood Sugar prediction" = "Last Blood Sugar prediction"; +"Last Blood Sugar prediction" = "آخر تنبؤ بمستوى الجلوكوز في الدم"; /* */ -"CurrentBG" = "CurrentBG"; +"CurrentBG" = "معدل الجلوكوز الحالي"; /* */ -"Sensor Info" = "Sensor Info"; +"Sensor Info" = "معلومات المستشعر"; /* */ -"Sensor Age" = "Sensor Age"; +"Sensor Age" = "عمر المستشعر"; /* */ -"Sensor Age Left" = "Sensor Age Left"; +"Sensor Age Left" = "عمر المستشعر المتبقي"; /* */ -"Sensor Endtime" = "Sensor Endtime"; +"Sensor Endtime" = "وقت انتهاء المستشعر"; /* */ -"Sensor State" = "Sensor State"; +"Sensor State" = "حالة المستشعر"; /* */ -"Sensor Serial" = "Sensor Serial"; +"Sensor Serial" = "الرقم التسلسلي للمستشعر"; /* */ -"Transmitter Info" = "Transmitter Info"; +"Transmitter Info" = "معلومات المُرسل"; /* */ -"Hardware" = "Hardware"; +"Hardware" = "العتاد"; /* */ -"Firmware" = "Firmware"; +"Firmware" = "البرنامج الثابت"; /* */ -"Connection State" = "Connection State"; +"Connection State" = "حالة الإتصال"; /* */ -"Transmitter Type" = "Transmitter Type"; +"Transmitter Type" = "نوع جهاز الإرسال"; /* */ -"Sensor Type" = "Sensor Type"; +"Sensor Type" = "نوع المستشعر"; /* */ -"Factory Calibration Parameters" = "Factory Calibration Parameters"; +"Factory Calibration Parameters" = "عوامل معايرة المصنع"; /* */ -"Valid for footer" = "Valid for footer"; +"Valid for footer" = "صالح لتذييل الصفحة"; /* */ -"Edit calibrations" = "Edit calibrations"; +"Edit calibrations" = "تعديل المعايرات"; /* */ "edit calibration clicked" = "edit calibration clicked"; /* */ -"Delete CGM" = "Delete CGM"; +"Delete CGM" = "حذف المستشعر"; /* */ -"Are you sure you want to remove this cgm from loop?" = "Are you sure you want to remove this cgm from loop?"; +"Are you sure you want to remove this cgm from loop?" = "هل أنت متأكد من أنك تريد إزالة هذا المستشعر من لووب؟"; /* */ -"There is no undo" = "There is no undo"; +"There is no undo" = "لا يوجد تراجع!"; /* */ -"Advanced" = "Advanced"; +"Advanced" = "متقدم"; /* */ -"Alarms" = "Alarms"; +"Alarms" = "التنبيهات"; /* */ -"Glucose Settings" = "Glucose Settings"; +"Glucose Settings" = "إعدادات الجلوكوز"; /* */ -"Notifications" = "Notifications"; +"Notifications" = "الإشعارات"; /* */ -"Export logs" = "Export logs"; +"Export logs" = "تصدير السجلات"; /* */ -"Export not available" = "Export not available"; +"Export not available" = "التصدير غير متوفر"; /* */ -"Log export requires ios 15" = "Log export requires ios 15"; +"Log export requires ios 15" = "تصدير السجل يتطلب iOS 15"; /* */ -"Got it!" = "Got it!"; +"Got it!" = "فهمت ذلك!"; /* */ -"Saved to %@" = "Saved to %@"; +"Saved to %@" = "تم الحفظ في %@"; /* */ -"No logs available" = "No logs available"; +"No logs available" = "لا توجد سجلات متاحة"; /* */ -"Glucose Notification visibility" = "Glucose Notification visibility"; +"Glucose Notification visibility" = "وضوح إشعار مستوى الجلوكوز"; /* */ -"Always Notify Glucose" = "Always Notify Glucose"; +"Always Notify Glucose" = "الاشعار بمستوى الجلوكوز دائمًا"; /* */ -"Notify per reading" = "Notify per reading"; +"Notify per reading" = "إشعار لكل قراءة"; /* */ -"Value" = "Value"; +"Value" = "القيمة"; /* */ -"Adds Phone Battery" = "Adds Phone Battery"; +"Adds Phone Battery" = "إضافة بطارية الهاتف"; /* */ -"Adds Transmitter Battery" = "Adds Transmitter Battery"; +"Adds Transmitter Battery" = "إضافة بطارية جهاز الإرسال"; /* */ "Also vibrate" = "Also vibrate"; @@ -1104,16 +1111,16 @@ Enact a temp Basal or a temp target */ "Sensor not found" = "Sensor not found"; /* */ -"Also play alert sound" = "Also play alert sound"; +"Also play alert sound" = "تشغيل التنبيه الصوتي أيضا"; /* */ -"Notification Settings" = "Notification Settings"; +"Notification Settings" = "إعدادات الإشعارات"; /* */ -"Found devices: %d" = "Found devices: %d"; +"Found devices: %d" = "الأجهزة التي تم العثور عليها: %d"; /* */ -"Backfill options" = "Backfill options"; +"Backfill options" = "خيارات الإملاء العكسية"; /* */ "Backfilling from trend is currently not well supported by Loop" = "Backfilling from trend is currently not well supported by Loop"; @@ -1243,7 +1250,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -1269,7 +1276,7 @@ Enact a temp Basal or a temp target */ "Save as your Normal Basal Rates" = "Save as your Normal Basal Rates"; /* */ -"Save on Pump" = "Save on Pump"; +"Save on Pump" = "الحفظ في المضخة"; /* Debug option view Pump History */ "Pump History" = "Pump History"; @@ -1299,7 +1306,7 @@ Enact a temp Basal or a temp target */ "Enacted announcements" = "Enacted announcements"; /* Debug option view Autotune */ -"Autotune" = "Autotune"; +"Autotune" = "ضبط الانسولين التلقائي"; /* Debug option view Target presets */ "Target presets" = "Target presets"; @@ -1467,7 +1474,7 @@ Enact a temp Basal or a temp target */ "Cancel Profile Override" = "Cancel Profile Override"; /* Alert */ -"Cancel Temp Target" = "Cancel Temp Target"; +"Cancel Temp Target" = "إلغاء الهدف المؤقت"; /* Alert */ "Return to Normal?" = "Return to Normal?"; @@ -1530,7 +1537,7 @@ Enact a temp Basal or a temp target */ "Save and continue" = "Save and continue"; /* */ -"Save as Preset" = "Save as Preset"; +"Save as Preset" = "حفظ كتعيين مسبق"; /* */ "Predictions" = "Predictions"; @@ -1636,7 +1643,7 @@ Enact a temp Basal or a temp target */ "Saving..." = "Saving..."; /* button title for saving low reservoir reminder */ -"Save" = "Save"; +"Save" = "إحفظ"; /* Alert title for error when updating confidence reminder preference */ "Failed to update confidence reminder preference." = "Failed to update confidence reminder preference."; @@ -1689,7 +1696,7 @@ Enact a temp Basal or a temp target */ "Change Pod now. Insulin delivery will stop in %1$@ or when no more insulin remains." = "Change Pod now. Insulin delivery will stop in %1$@ or when no more insulin remains."; /* Label text for temporary basal rate summary */ -"Rate" = "Rate"; +"Rate" = "معدّل"; /* Summary string for temporary basal rate configuration page */ "%1$@ for %2$@" = "%1$@ for %2$@"; @@ -1872,7 +1879,7 @@ Enact a temp Basal or a temp target */ "Interval" = "Interval"; /* Median loop interval */ -"Duration" = "Duration"; +"Duration" = "المدة"; /* "Display SD */ "Display SD instead of CV" = "Display SD instead of CV"; @@ -1953,7 +1960,7 @@ Enact a temp Basal or a temp target */ "Man" = "Man"; /* Sex dropdown menu option */ -"Other" = "Other"; +"Other" = "أخرى"; /* Sex dropdown menu option */ "Secret" = "Secret"; @@ -2515,6 +2522,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings index f41e0bf139..73a5692ddb 100644 --- a/FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings @@ -50,7 +50,7 @@ "Suggested at" = "Suggested at"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error at"; +"Status at" = "Status at"; /* Home title */ "Home" = "Home"; diff --git a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings index 25a2d8e26f..2ee3fa6954 100644 --- a/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Anbefalet"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Fejl ved"; +"Status at" = "Fejl ved"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Måltidsoversigt"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fedt Måltid"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Fuld Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Maks Kulhydrater"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pumpe Indstillinger"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,15 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +<<<<<<< HEAD +======= +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + +>>>>>>> dev /* Setting title */ "Bolus Calculator" = "Bolus Lommeregner"; diff --git a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings index 6945c414ce..2452c2a48e 100644 --- a/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Vorgeschlagen um"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Fehler um"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Mahlzeitenübersicht"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fettige Mahlzeit"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Behandlung"; + /* For the Bolus View pop-up */ "Full Bolus" = "Voller Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Kohlenhydrate"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Höchstmenge an Kohlenhydraten (g) pro Eintrag"; + /* */ "Pump Settings" = "Pumpeneinstellungen"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Die neue Empfehlung kann nicht angewendet werden, da zurzeit ein Bolus verabreicht wird. Warten Sie bitte die nächste Schleife ab."; /* Pump Error */ -"Error: Pump is Busy." = "Fehler: Die Pumpe ist beschäftigt."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -1962,7 +1968,7 @@ Enact a temp Basal or a temp target */ "Birth Date" = "Geburtsdatum"; /* Share and Backup info text */ -"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nWenn Sie \"Freigeben und Sichern\" aktivieren, werden täglich Sicherungen Ihrer Einstellungen und Statistiken in der Online-Datenbank erstellt.\n\nStellen Sie daher sicher, dass Sie Ihr Wiederherstellungs-Token unten kopieren und speichern. Das Wiederherstellungs-Token wird benötigt, um Ihre Einstellungen auf ein anderes Telefon zu importieren, wenn Sie die Onboarding view verwenden."; /* Section title if sharing off */ "Share Bare Minimum" = "Nur das absolute Minimum teilen"; @@ -1971,7 +1977,7 @@ Enact a temp Basal or a temp target */ "Just iAPS version number" = "Nur iAPS Versionsnummer"; /* Share info text */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Alle Informationen, die Sie freigeben möchten, werden anonym hochgeladen. Um doppelte Uploads zu vermeiden, werden die Daten mit einer eindeutigen, zufälligen Zeichenfolge identifiziert, die auf Ihrem Telefon gespeichert ist."; /* Token section title */ "Your recovery token" = "Ihr Wiederherstellungs-Token"; @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Beim Scrollen nie das kleine Glukosediagramm anzeigen"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Hypo Behandlung deaktivieren"; + +/* UI/UX option */ +"Display Glucose Delta" = "Glukose-Delta anzeigen"; + /* Setting title */ "Bolus Calculator" = "Bolus-Rechner"; diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index 391ee8e0e3..d37191360d 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggested at"; /* Headline in enacted pop up (at: at what time) */ - "Error at" = "Error at"; + "Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Meal Summary"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fatty Meal"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Full Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pump Settings"; @@ -1255,7 +1261,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ @@ -2533,6 +2539,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings index c0064fd928..b04354872d 100644 --- a/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Sugerido a las"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error en"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Resumen de la comida"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Comida Grasienta"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Bolo Completo"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Carbohidratos máximos"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Límites de la bomba"; @@ -1244,7 +1250,7 @@ Solamente puedes emparejar una app con el sensor vía bluetooth. A continuación "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "No se ha ejecutado el cálculo recomendado para el nuevo ciclo porque hay un bolo en progreso. Espere al siguiente ciclo del lazo"; /* Pump Error */ -"Error: Pump is Busy." = "Error: bomba ocupada."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2632,6 +2638,12 @@ Un límite Autosens.máx > 1,5 no es recomendable cuando se utiliza la función /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Calculador de bolo"; diff --git a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings index cd03eaf01e..b743542340 100644 --- a/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Ehdotettu klo"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Virhe klo"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Aterian yhteenveto"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Rasvainen ruoka"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Täysi bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pump Settings"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings index 6f645af641..b8f5be3999 100644 --- a/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggéré à"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Erreur à"; +"Status at" = "État de"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Résumé du repas"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Repas gras"; +/* Bolus option */ +"Hypo Treatment" = "Traitement d'hypoglycémie"; + /* For the Bolus View pop-up */ "Full Bolus" = "Bolus entier"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Glucides max"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Quantité maximale de glucides (g) que vous pouvez ajouter à chaque entrée"; + /* */ "Pump Settings" = "Paramètres de la pompe"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Impossible d'appliquer la nouvelle recommandation, car l'administration d'un bolus est en cours. Attendez la prochaine boucle."; /* Pump Error */ -"Error: Pump is Busy." = "Erreur: La pompe est occupée."; +"Pump is Busy." = "La pompe est occupée."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -1962,7 +1968,7 @@ Enact a temp Basal or a temp target */ "Birth Date" = "Date de naissance"; /* Share and Backup info text */ -"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nSi vous activez \"Partage et Sauvegarde\", des sauvegardes quotidiennes de vos paramètres et statistiques seront effectuées dans la base de données en ligne.\n\nAssurez-vous de copier et d'enregistrer votre jeton de récupération ci-dessous. Le jeton de récupération est requis pour importer vos paramètres sur un autre téléphone lorsque vous utilisez le tutoriel de mise en marche."; /* Section title if sharing off */ "Share Bare Minimum" = "Partager le minimum"; @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Ne jamais afficher le petit graphique de glucose lors du défilement"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Désactiver les traitements d'hypoglycémie"; + +/* UI/UX option */ +"Display Glucose Delta" = "Afficher l'écart de glycémie"; + /* Setting title */ "Bolus Calculator" = "Calculateur de Bolus"; diff --git a/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings index e055b06a60..768904eeb1 100644 --- a/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggested at"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error at"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Meal Summary"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fatty Meal"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Full Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pump Settings"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings index 4152c9430a..ea5777a9e2 100644 --- a/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Ajánlva"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Hiba"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Étel összegzése"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Zsíros étel"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Teljes bólus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Maximum szénhidrát"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pumpa beállítások"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Soha ne jelenítse meg a kis glükózdiagramot görgetés közben"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings index 5ec32b4c9c..71682db831 100644 --- a/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggerito alle"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Errore a"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Riepilogo Pasto"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Pasto Grasso"; +/* Bolus option */ +"Hypo Treatment" = "Trattamento ipoglicemia"; + /* For the Bolus View pop-up */ "Full Bolus" = "Bolo Completo"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Carboidrati massimi"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Quantità massima di carboidrati (g) che è possibile aggiungere alla volta"; + /* */ "Pump Settings" = "Microinfusore"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Non è possibile attuare un nuovo ciclo loop, perché un bolo è in corso. Attendere il ciclo loop successivo"; /* Pump Error */ -"Error: Pump is Busy." = "Errore: la pompa sta già lavorando."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -1935,16 +1941,16 @@ Enact a temp Basal or a temp target */ "Sharing" = "Condivisione"; /* Share and Backup page header */ -"Share and Backup" = "Share and Backup"; +"Share and Backup" = "Condivisione e backup"; /* Section 1 title */ -"Upload settings and statistics" = "Upload settings and statistics"; +"Upload settings and statistics" = "Carica le impostazioni e le statistiche"; /* On-off toggle */ -"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; +"Share and Backup all of your Settings and Statistics" = "Condividi e salva tutte le tue impostazioni e statistiche"; /* Title of dropdown menu */ -"Sex" = "Sex"; +"Sex" = "Genere"; /* Sex dropdown menu option */ "Woman" = "Donna"; @@ -1962,7 +1968,7 @@ Enact a temp Basal or a temp target */ "Birth Date" = "Data di nascita"; /* Share and Backup info text */ -"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nSe abiliti \"Condividi e Backup\" i backup giornalieri delle tue impostazioni e delle tue statistiche saranno effettuati sul database online.\n\nAssicurati di copiare e salvare il tuo token di recupero qui sotto. Il token di recupero è necessario per importare le tue impostazioni su un altro telefono quando si utilizza la vista onboarding."; /* Section title if sharing off */ "Share Bare Minimum" = "Condividi Minimo Nullo"; @@ -1971,19 +1977,19 @@ Enact a temp Basal or a temp target */ "Just iAPS version number" = "Solo il numero di versione iAPS"; /* Share info text */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Ogni bit di informazione che si sceglie di condividere viene caricato in forma anonima. Per evitare duplicati caricamenti, i dati vengono identificati con una stringa casuale univoca salvata sul telefono, il token di recupero."; /* Token section title */ -"Your recovery token" = "Your recovery token"; +"Your recovery token" = "Il tuo token di recupero"; /* Token display button */ "Tap to display" = "Tocca per visualizzare"; /* Token copy button */ -"Long press to copy" = "Long press to copy"; +"Long press to copy" = "Premi a lungo per copiare"; /* Link to statistics button */ -"View Personal Statistics" = "View Personal Statistics"; +"View Personal Statistics" = "Mostra Statistiche Personali"; /* */ "Your identifier" = "Il tuo identificativo"; @@ -2516,6 +2522,12 @@ Per gli utenti più giovani si consiglia di iniziare con un dosaggio ancora più /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Non visualizzare mai il grafico piccolo del glucosio durante lo scorrimento"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disabilita trattamenti Ipoglicemie"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Calcolatore Bolo"; diff --git a/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings index e3f454b286..e1e28545a9 100644 --- a/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Foreslått kl."; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Feil kl."; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Oppsummering av måltid"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fett måltid"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Full Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Maksimalt antall karbohydrater"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pumpeinnstillinger"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Boluskalkulator"; diff --git a/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings index b60f7ab111..79aef53abc 100644 --- a/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Voorgesteld op"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Foutmelding op"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Maaltijd overzicht"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Vette maaltijd"; +/* Bolus option */ +"Hypo Treatment" = "Hypobehandeling"; + /* For the Bolus View pop-up */ "Full Bolus" = "Volledige bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max koolhydraten"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximale hoeveelheid koolhydraten (g) die je elke invoer kunt toevoegen"; + /* */ "Pump Settings" = "Pomp instellingen"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Kan het nieuwe loopcyclus advies niet uitvoeren, omdat er een bolus bezig is. Wacht op de volgende cyclus"; /* Pump Error */ -"Error: Pump is Busy." = "Fout: Pomp is bezig."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -1935,16 +1941,16 @@ Enact a temp Basal or a temp target */ "Sharing" = "Delen van statistieken"; /* Share and Backup page header */ -"Share and Backup" = "Share and Backup"; +"Share and Backup" = "Delen en back-up"; /* Section 1 title */ -"Upload settings and statistics" = "Upload settings and statistics"; +"Upload settings and statistics" = "Upload instellingen en statistieken"; /* On-off toggle */ -"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; +"Share and Backup all of your Settings and Statistics" = "Deel en back-up al uw instellingen en statistieken"; /* Title of dropdown menu */ -"Sex" = "Sex"; +"Sex" = "Geslacht"; /* Sex dropdown menu option */ "Woman" = "Vrouw"; @@ -1962,7 +1968,7 @@ Enact a temp Basal or a temp target */ "Birth Date" = "Geboortedatum"; /* Share and Backup info text */ -"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nAls u \"Delen en back-uppen\" inschakelt, worden er dagelijks back-ups van uw instellingen en statistieken gemaakt in een online database.\n\nZorg ervoor dat u uw hersteltoken hieronder kopieert en opslaat. Het hersteltoken is nodig om uw instellingen te importeren naar een andere telefoon wanneer u de onboarding-weergave gebruikt."; /* Section title if sharing off */ "Share Bare Minimum" = "Deel alleen het minimale"; @@ -1971,19 +1977,19 @@ Enact a temp Basal or a temp target */ "Just iAPS version number" = "Alleen iAPS versienummer"; /* Share info text */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Elk stukje informatie dat je wilt delen, wordt anoniem geüpload. Om dubbele uploads te voorkomen, worden de gegevens geïdentificeerd met een unieke willekeurige tekenreeks die op je telefoon is opgeslagen, het hersteltoken."; /* Token section title */ -"Your recovery token" = "Your recovery token"; +"Your recovery token" = "Je herstelcode"; /* Token display button */ "Tap to display" = "Tik om weer te geven"; /* Token copy button */ -"Long press to copy" = "Long press to copy"; +"Long press to copy" = "Lang ingedrukt houden om te kopiëren"; /* Link to statistics button */ -"View Personal Statistics" = "View Personal Statistics"; +"View Personal Statistics" = "Persoonlijke statistieken bekijken"; /* */ "Your identifier" = "Jouw identificatie"; @@ -2519,6 +2525,12 @@ Eenvoudig gezegd, de Dynamische Carb Ratio past de koolhydraatverhouding aan op /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Laat de kleine glucosegrafiek nooit zien tijdens het scrollen"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Hypobehandelingen uitschakelen"; + +/* UI/UX option */ +"Display Glucose Delta" = "Glucose delta weergeven"; + /* Setting title */ "Bolus Calculator" = "Bolus calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings index 7d64893723..ca19bbd88b 100644 --- a/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggested at"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error at"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Meal Summary"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fatty Meal"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Full Bolus"; @@ -478,6 +481,9 @@ Połączono z Nightscout!"; /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pump Settings"; @@ -1245,7 +1251,7 @@ Połączono z Nightscout!"; "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2517,6 +2523,12 @@ Połączono z Nightscout!"; /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings index ced78bc51d..ab970c0880 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggested at"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error at"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Meal Summary"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fatty Meal"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Full Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Ajustes Bomba"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings index a00bbc4143..975d77bb16 100644 --- a/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggested at"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error at"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Meal Summary"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fatty Meal"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Full Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Ajustes Bomba"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings index 04258d9310..29f779a168 100644 --- a/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Предложено в"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Ошибка в"; +"Status at" = "Статус в"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Итог питания"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Жирная пища"; +/* Bolus option */ +"Hypo Treatment" = "Гипогликемия"; + /* For the Bolus View pop-up */ "Full Bolus" = "Полный болюс"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Максимум углеводов"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Максимум углеводов (г), который можно внести в запись"; + /* */ "Pump Settings" = "Настройки помпы"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Не удается выполнить рекомендацию по новому циклу петли, так как вводится болюс. Подождите новый цикл петли"; /* Pump Error */ -"Error: Pump is Busy." = "Ошибка: Помпа занята."; +"Pump is Busy." = "Помпа занята."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -1962,7 +1968,7 @@ Enact a temp Basal or a temp target */ "Birth Date" = "День рождения"; /* Share and Backup info text */ -"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nЕсли включить \"Общий доступ и резервное копирование\", то ежедневные резервные копии ваших настроек и статистики будут сохраняться в онлайн-базе данных.\n\nОбязательно скопируйте и сохраните свой токен восстановления, указанный ниже. Токен восстановления необходим для импорта настроек на другой телефон при использовании встроенного режима просмотра."; /* Section title if sharing off */ "Share Bare Minimum" = "Минимально делиться"; @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Не отображать график уровня глюкозы при прокрутке"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Отключить гипогликемии"; + +/* UI/UX option */ +"Display Glucose Delta" = "Отображать дельту глюкозы"; + /* Setting title */ "Bolus Calculator" = "Калькулятор болюса"; diff --git a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings index 14fc01b649..07c354e57d 100644 --- a/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Navrhnuté o"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Chyba o"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Sumár jedla"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Mastné jedlo"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Celková dávka"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Maximum sacharidov"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Nastavenia pumpy"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Kalkulátor bolusu"; diff --git a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings index 2d91a3d5f8..f78b6dc1df 100644 --- a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Föreslaget vid"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Fel vid"; +"Status at" = "Status kl"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Måltidssammanfattning"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fet mat"; +/* Bolus option */ +"Hypo Treatment" = "Behandling av lågt blodsocker"; + /* For the Bolus View pop-up */ "Full Bolus" = "Total mängd"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max antal kolhydrater"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximal antal kolhydrater (g) du kan lägga till varje måltid"; + /* */ "Pump Settings" = "Pumpinställningar"; @@ -1255,7 +1261,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Kan inte utföra ny loop-rekommendation just nu, pga en pågående bolus. Vänta till nästa loop"; /* Pump Error */ -"Error: Pump is Busy." = "Fel: pump är upptagen."; +"Pump is Busy." = "Pump är upptagen."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2365,7 +2371,7 @@ Enact a temp Basal or a temp target */ "Minimum duration in minutes for new SMB since last SMB or manual bolus" = "Minsta tid i minuter för ny automatisk bolus sedan senaste bolus eller automatisk bolus"; /* Headline "Bolus Increment" */ -"Bolus Increment" = "Minsta bolusmångd"; +"Bolus Increment" = "Minsta bolusmängd"; /* "Bolus Increment" */ "Smallest enacted SMB amount. Minimum amount for Omnipod pumps is 0.05 U, whereas for Medtronic pumps it differs for various models, from 0.025 U to 0.10 U. Please check the minimum bolus amount which can be delivered by your pump. The default value is 0.1." = "Minsta mängd för Omnipod-poddar är 0,05 enheter, medan det för Medtronic-pumpar skiljer sig åt för olika modeller, från 0.25 IE till 0,10 IE. Kontrollera den minsta bolusmängd som kan ges av din pump för denna inställning. Standardvärdet är 0,1 enheter."; @@ -2522,11 +2528,17 @@ Enact a temp Basal or a temp target */ "Horizontal Scroll View Visible hours" = "Antal synliga timmar i hemfönstret"; /* UI/UX option */ -"Display Time Interval Setting Button" = "Visa knapp för tidsintervell"; +"Display Time Interval Setting Button" = "Visa knapp för tidsintervall"; /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Dölj det lilla glukosdiagrammet när du scrollar"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Visa inte behandling av lågt blodsocker"; + +/* UI/UX option */ +"Display Glucose Delta" = "Visa exakt glukosförändring"; + /* Setting title */ "Bolus Calculator" = "Boluskalkylator"; diff --git a/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings index a771697a70..b2521fa3b5 100644 --- a/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggested at"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error at"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Meal Summary"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fatty Meal"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Full Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "Pompa Ayarları"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings index a711c77358..8428dad542 100644 --- a/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Запропоновано на"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Помилка в"; +"Status at" = "Статус в"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Резюме харчування"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Жирна їжа"; +/* Bolus option */ +"Hypo Treatment" = "Гіпотерапія"; + /* For the Bolus View pop-up */ "Full Bolus" = "Повний болюс"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Максимум вуглеводів"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Максимальна кількість вуглеводів (г), яку можна додати для кожного запису"; + /* */ "Pump Settings" = "Налаштування Помпи"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Неможливо виконати рекомендацію щодо нового циклу петлі, оскільки триває болюс. Зачекайте наступного циклу петлі"; /* Pump Error */ -"Error: Pump is Busy." = "Помилка: Помпа зайнята."; +"Pump is Busy." = "Помпа зайнята"; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -1962,7 +1968,7 @@ Enact a temp Basal or a temp target */ "Birth Date" = "Дата народження"; /* Share and Backup info text */ -"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nЯкщо ви ввімкнете «Поділитися та створити резервну копію», щоденні резервні копії ваших налаштувань і статистики будуть створені в онлайн-базі даних.\n\nОбов’язково скопіюйте та збережіть токен відновлення нижче. Токен відновлення потрібен для імпорту ваших налаштувань на інший телефон під час перегляду адаптації."; /* Section title if sharing off */ "Share Bare Minimum" = "Мінімум розподілу"; @@ -2515,6 +2521,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Ніколи не відображайте маленьку діаграму рівня глюкози під час прокручування"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Вимкнути гіпотерапію"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Калькулятор Болюса"; diff --git a/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings index b69fb59dc6..99fec2fa5c 100644 --- a/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Đề xuất ở"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Lỗi ở"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Tóm tắt bữa ăn"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Bữa ăn nhiều chất béo"; +/* Bolus option */ +"Hypo Treatment" = "Chống hạ đường huyết"; + /* For the Bolus View pop-up */ "Full Bolus" = "Liều bolus đầy đủ"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Khối lượng carbs tối đa"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Lượng carbs tối đa (g) bạn có thể thêm vào mỗi mục nhập"; + /* */ "Pump Settings" = "Cấu hình bơm"; @@ -510,7 +516,7 @@ Enact a temp Basal or a temp target */ "g" = "g"; /* when 0 U/hr */ -"0 U/hr" = "U/hr"; +"0 U/hr" = "0 U/hr"; /* abbreviation for days */ "d" = "d"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Không thể thực hiện đề xuất chu kỳ vòng lặp mới vì đang tiến hành một đợt Bolus. Đợi chu kỳ vòng lặp tiếp theo"; /* Pump Error */ -"Error: Pump is Busy." = "Lỗi: Máy bơm đang bận."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -1556,7 +1562,7 @@ Enact a temp Basal or a temp target */ "\n\nTap 'Add' to continue with selected amount." = "\n\nNhấn 'Thêm' để tiếp tục với số lượng đã chọn."; /* */ -"Eventual Glucose" = "Glucose hiện tại"; +"Eventual Glucose" = "Eventual Glucose"; /* */ "Please wait" = "Vui lòng chờ"; @@ -1586,25 +1592,25 @@ Enact a temp Basal or a temp target */ "Hide" = "Ẩn"; /* Bolus pop-up / Alert string. Make translations concise! */ -"Eventual Glucose > Target Glucose, but glucose is predicted to drop down to " = "Glucose hiện tại lớn hơn Glucose mục tiêu, nhưng glucose được dự đoán trước tiên sẽ giảm xuống "; +"Eventual Glucose > Target Glucose, but glucose is predicted to drop down to " = "Eventual Glucose > Target Glucose, nhưng glucose được dự đoán trước tiên sẽ giảm xuống "; /* Bolus pop-up / Alert string. Make translations concise! */ "which is below your Threshold (" = "nằm dưới Ngưỡng của bạn ("; /* Bolus pop-up / Alert string. Make translations concise! */ -"Eventual Glucose > Target Glucose, but glucose is climbing slower than expected. Expected: " = "Glucose hiện tại lớn hơn Glucose mục tiêu, nhưng Glucose tăng chậm hơn dự kiến. Hy vọng: "; +"Eventual Glucose > Target Glucose, but glucose is climbing slower than expected. Expected: " = "Eventual Glucose > Target Glucose, nhưng Glucose tăng chậm hơn dự kiến. Hy vọng: "; //* Bolus pop-up / Alert string. Make translations concise! */ ". Climbing: " = ". Đang tăng lên: "; /* Bolus pop-up / Alert string. Make translations concise! */ -"Eventual Glucose > Target Glucose, but glucose is falling faster than expected. Expected: " = "Glucose hiện tại lớn hơn Glucose mục tiêu, nhưng glucose đang giảm nhanh hơn dự kiến. Hy vọng: "; +"Eventual Glucose > Target Glucose, but glucose is falling faster than expected. Expected: " = "Eventual Glucose > Target Glucose, nhưng glucose đang giảm nhanh hơn dự kiến. Hy vọng: "; /* Bolus pop-up / Alert string. Make translations concise! */ ". Falling: " = ". Đang giảm xuống: "; /* Bolus pop-up / Alert string. Make translations concise! */ -"Eventual Glucose > Target Glucose, but glucose is changing faster than expected. Expected: " = "Glucose hiện tại lớn hơn Glucose mục tiêu, nhưng glucose đang giảm nhanh hơn dự kiến. Hy vọng: "; +"Eventual Glucose > Target Glucose, but glucose is changing faster than expected. Expected: " = "Eventual Glucose > Target Glucose, nhưng glucose đang giảm nhanh hơn dự kiến. Hy vọng: "; /* Bolus pop-up / Alert string. Make translations concise! */ ". Changing: " = ". Đang thay đổi: "; @@ -1935,16 +1941,16 @@ Enact a temp Basal or a temp target */ "Sharing" = "Chia sẻ"; /* Share and Backup page header */ -"Share and Backup" = "Share and Backup"; +"Share and Backup" = "Chia sẻ và sao lưu"; /* Section 1 title */ -"Upload settings and statistics" = "Upload settings and statistics"; +"Upload settings and statistics" = "Tải lên cài đặt và số liệu thống kê"; /* On-off toggle */ -"Share and Backup all of your Settings and Statistics" = "Share and Backup all of your Settings and Statistics"; +"Share and Backup all of your Settings and Statistics" = "Chia sẻ và sao lưu tất cả các thiết lập và số liệu thống kê của bạn"; /* Title of dropdown menu */ -"Sex" = "Sex"; +"Sex" = "Giới tính"; /* Sex dropdown menu option */ "Woman" = "Nữ"; @@ -1962,7 +1968,7 @@ Enact a temp Basal or a temp target */ "Birth Date" = "Ngày sinh"; /* Share and Backup info text */ -"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view."; +"\nIf you enable \"Share and Backup\" daily backups of your settings and statistics will be made to online database.\n\nMake sure to copy and save your recovery token below. The recovery token is required to import your settings to another phone when using the onboarding view." = "\nNếu bạn bật \"Chia sẻ và Sao lưu\", các bản sao lưu hàng ngày của cài đặt và số liệu thống kê của bạn sẽ được thực hiện vào cơ sở dữ liệu trực tuyến.\n\nHãy đảm bảo sao chép và lưu mã thông báo khôi phục của bạn bên dưới. Mã thông báo khôi phục là bắt buộc để nhập cài đặt của bạn vào điện thoại khác khi sử dụng chế độ xem tích hợp."; /* Section title if sharing off */ "Share Bare Minimum" = "Chia sẻ ở mức tối thiểu"; @@ -1971,19 +1977,19 @@ Enact a temp Basal or a temp target */ "Just iAPS version number" = "Chỉ phiên bản iAPS"; /* Share info text */ -"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token."; +"Every bit of information you choose to share is uploaded anonymously. To prevent duplicate uploads, the data is identified with a unique random string saved on your phone, the recovery token." = "Mọi thông tin bạn chọn chia sẻ đều được tải lên ẩn danh. Để ngăn chặn việc tải lên trùng lặp, dữ liệu được xác định bằng một chuỗi ngẫu nhiên duy nhất được lưu trên điện thoại của bạn, mã thông báo khôi phục."; /* Token section title */ -"Your recovery token" = "Your recovery token"; +"Your recovery token" = "Mã thông báo khôi phục của bạn"; /* Token display button */ "Tap to display" = "Chạm để hiển thị"; /* Token copy button */ -"Long press to copy" = "Long press to copy"; +"Long press to copy" = "Nhấn và giữ để sao chép"; /* Link to statistics button */ -"View Personal Statistics" = "View Personal Statistics"; +"View Personal Statistics" = "Xem Thống kê cá nhân"; /* */ "Your identifier" = "Mã định danh của bạn"; @@ -2516,6 +2522,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Không bao giờ hiển thị biểu đồ glucose nhỏ khi cuộn"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Vô hiệu hóa điều trị hạ đường huyết"; + +/* UI/UX option */ +"Display Glucose Delta" = "Hiển thị Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Tính toán Bolus"; diff --git a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings index c2bc5e9730..e3bd18e11c 100644 --- a/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Suggested at" = "Suggested at"; /* Headline in enacted pop up (at: at what time) */ -"Error at" = "Error at"; +"Status at" = "Status at"; /* Bolus View Meal Summary Header */ "Meal Summary" = "Meal Summary"; @@ -112,6 +112,9 @@ /* For the Bolus View pop-up */ "Fatty Meal" = "Fatty Meal"; +/* Bolus option */ +"Hypo Treatment" = "Hypo Treatment"; + /* For the Bolus View pop-up */ "Full Bolus" = "Full Bolus"; @@ -476,6 +479,9 @@ Enact a temp Basal or a temp target */ /* Max setting */ "Max Carbs" = "Max Carbs"; +/* Max carbs description */ +"Maximum amount of carbs (g) you can add each entry" = "Maximum amount of carbs (g) you can add each entry"; + /* */ "Pump Settings" = "泵设置"; @@ -1243,7 +1249,7 @@ Enact a temp Basal or a temp target */ "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle" = "Can't enact the new loop cycle recommendation, because a Bolus is in progress. Wait for next loop cycle"; /* Pump Error */ -"Error: Pump is Busy." = "Error: Pump is Busy."; +"Pump is Busy." = "Pump is Busy."; /* -------------- Developer settings ---------------------- */ /* Debug options */ @@ -2517,6 +2523,12 @@ Enact a temp Basal or a temp target */ /* UI/UX option */ "Never display the small glucose chart when scrolling" = "Never display the small glucose chart when scrolling"; +/* UI/UX option */ +"Disable Hypo Treatments" = "Disable Hypo Treatments"; + +/* UI/UX option */ +"Display Glucose Delta" = "Display Glucose Delta"; + /* Setting title */ "Bolus Calculator" = "Bolus Calculator"; diff --git a/FreeAPS/Sources/Models/Configs.swift b/FreeAPS/Sources/Models/Configs.swift index e9c0760965..80b7ab8060 100644 --- a/FreeAPS/Sources/Models/Configs.swift +++ b/FreeAPS/Sources/Models/Configs.swift @@ -40,6 +40,7 @@ extension Font { static let previewSmall = Font.custom("PreviewSmallFont", size: 14) static let previewNormal = Font.custom("PreviewNormalFont", size: 16) static let previewHeadline = Font.custom("PreviewHeadlineFont", size: 18) + static let previewExtraBig = Font.custom("PreviewHeadlineFont", size: 20) static let extraSmall = Font.custom("ExtraSmallFont", size: 12) static let suggestionHeadline = Font.custom("SuggestionHeadlineFont", fixedSize: 20) @@ -48,6 +49,7 @@ extension Font { static let suggestionSmallParts = Font.custom("SuggestionSmallPartsFont", fixedSize: 16) static let glucoseFont = Font.custom("SuggestionSmallPartsFont", size: 45) + static let glucoseFontMdDl = Font.custom("SuggestionSmallPartsFont", size: 40) static let glucoseSmallFont = Font.custom("SuggestionSmallPartsFont", size: 24) static let bolusProgressStopFont = Font.custom("BolusProgressStop", fixedSize: 24) diff --git a/FreeAPS/Sources/Models/FreeAPSSettings.swift b/FreeAPS/Sources/Models/FreeAPSSettings.swift index b722f1f908..968a47dc82 100644 --- a/FreeAPS/Sources/Models/FreeAPSSettings.swift +++ b/FreeAPS/Sources/Models/FreeAPSSettings.swift @@ -68,6 +68,8 @@ struct FreeAPSSettings: JSON, Equatable { var birthDate = Date.distantPast // var sex: Sex = .secret var sexSetting: Int = 3 + var disableHypoTreatment: Bool = false + var displayDelta: Bool = false } extension FreeAPSSettings: Decodable { @@ -345,6 +347,14 @@ extension FreeAPSSettings: Decodable { settings.sexSetting = sexSetting } + if let disableHypoTreatment = try? container.decode(Bool.self, forKey: .disableHypoTreatment) { + settings.disableHypoTreatment = disableHypoTreatment + } + + if let displayDelta = try? container.decode(Bool.self, forKey: .displayDelta) { + settings.displayDelta = displayDelta + } + self = settings } } diff --git a/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift b/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift index b9aa0b11a2..09a2999754 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift @@ -7,6 +7,8 @@ extension AddCarbs { @Injected() var carbsStorage: CarbsStorage! @Injected() var apsManager: APSManager! @Injected() var settings: SettingsManager! + @Injected() var nightscoutManager: NightscoutManager! + @Published var carbs: Decimal = 0 @Published var date = Date() @Published var protein: Decimal = 0 @@ -21,16 +23,20 @@ extension AddCarbs { @Published var id_: String = "" @Published var summary: String = "" @Published var skipBolus: Bool = false + @Published var hypoTreatment: Bool = false + @Published var disableHypoTreatment: Bool = false let now = Date.now let coredataContext = CoreDataStack.shared.persistentContainer.viewContext + let coredataContextBackground = CoreDataStack.shared.persistentContainer.newBackgroundContext() override func subscribe() { carbsRequired = provider.suggestion?.carbsReq maxCarbs = settings.settings.maxCarbs skipBolus = settingsManager.settings.skipBolusScreenAfterCarbs useFPUconversion = settingsManager.settings.useFPUconversion + disableHypoTreatment = settingsManager.settings.disableHypoTreatment } func add(_ continue_: Bool, fetch: Bool) { @@ -54,7 +60,9 @@ extension AddCarbs { )] carbsStorage.storeCarbs(carbsToStore) - if skipBolus, !continue_, !fetch { + if hypoTreatment { hypo() } + + if (skipBolus && !continue_ && !fetch) || hypoTreatment { apsManager.determineBasalSync() showModal(for: nil) } else if carbs > 0 { @@ -207,5 +215,50 @@ extension AddCarbs { print("meals 1: ID: " + (save.id ?? "").description + " FPU ID: " + (save.fpuID ?? "").description) } } + + private func hypo() { + let os = OverrideStorage() + + // Cancel any eventual Other Override already active + if let activeOveride = os.fetchLatestOverride().first { + let presetName = os.isPresetName() + // Is the Override a Preset? + if let preset = presetName { + if let duration = os.cancelProfile() { + // Update in Nightscout + nightscoutManager.editOverride(preset, duration, activeOveride.date ?? Date.now) + } + } else if activeOveride.isPreset { // Because hard coded Hypo treatment isn't actually a preset + if let duration = os.cancelProfile() { + nightscoutManager.editOverride("📉", duration, activeOveride.date ?? Date.now) + } + } else { + let nsString = activeOveride.percentage.formatted() != "100" ? activeOveride.percentage + .formatted() + " %" : "Custom" + if let duration = os.cancelProfile() { + nightscoutManager.editOverride(nsString, duration, activeOveride.date ?? Date.now) + } + } + } + + // Enable New Override + let override = OverridePresets(context: coredataContextBackground) + override.percentage = 90 + override.smbIsOff = true + override.duration = 45 + override.name = "📉" + override.advancedSettings = true + override.target = 117 + override.date = Date.now + override.indefinite = false + + os.overrideFromPreset(override, UUID().uuidString) + // Upload to Nightscout + nightscoutManager.uploadOverride( + "📉", + Double(45), + override.date ?? Date.now + ) + } } } diff --git a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift index d3ebed4bfc..dc124685c8 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift @@ -75,7 +75,6 @@ extension AddCarbs { // Time HStack { - let now = Date.now Text("Time") Spacer() if !pushed { @@ -111,6 +110,11 @@ extension AddCarbs { .popover(isPresented: $isPromptPresented) { presetPopover } + + // Optional Hypo Treatment + if state.carbs > 0, !state.disableHypoTreatment { + Toggle("Hypo Treatment", isOn: $state.hypoTreatment) + } } Section { @@ -118,7 +122,11 @@ extension AddCarbs { button.toggle() if button { state.add(override, fetch: editMode) } } - label: { Text(((state.skipBolus && !override && !editMode) || state.carbs <= 0) ? "Save" : "Continue") } + label: { + Text( + ((state.skipBolus && !override && !editMode) || state.carbs <= 0 || state.hypoTreatment) ? "Save" : + "Continue" + ) } .disabled(empty) .frame(maxWidth: .infinity, alignment: .center) }.listRowBackground(!empty ? Color(.systemBlue) : Color(.systemGray4)) @@ -128,6 +136,7 @@ extension AddCarbs { mealPresets } } + .compactSectionSpacing() .dynamicTypeSize(...DynamicTypeSize.xxLarge) .onAppear { configureView { diff --git a/FreeAPS/Sources/Modules/Home/HomeStateModel.swift b/FreeAPS/Sources/Modules/Home/HomeStateModel.swift index adb27961c9..e05837b441 100644 --- a/FreeAPS/Sources/Modules/Home/HomeStateModel.swift +++ b/FreeAPS/Sources/Modules/Home/HomeStateModel.swift @@ -81,6 +81,8 @@ extension Home { @Published var maxBolusValue: Decimal = 1 @Published var useInsulinBars: Bool = false @Published var iobData: [IOBData] = [] + @Published var carbData: Decimal = 0 + @Published var iobs: Decimal = 0 @Published var neg: Int = 0 @Published var tddChange: Decimal = 0 @Published var tddAverage: Decimal = 0 @@ -89,6 +91,7 @@ extension Home { @Published var tdd3DaysAgo: Decimal = 0 @Published var tddActualAverage: Decimal = 0 @Published var skipGlucoseChart: Bool = false + @Published var displayDelta: Bool = false let coredataContext = CoreDataStack.shared.persistentContainer.viewContext @@ -141,6 +144,7 @@ extension Home { maxBolus = settingsManager.pumpSettings.maxBolus useInsulinBars = settingsManager.settings.useInsulinBars skipGlucoseChart = settingsManager.settings.skipGlucoseChart + displayDelta = settingsManager.settings.displayDelta broadcaster.register(GlucoseObserver.self, observer: self) broadcaster.register(SuggestionObserver.self, observer: self) @@ -274,6 +278,10 @@ extension Home { // Update in Nightscout nightscoutManager.editOverride(preset, duration, activeOveride.date ?? Date.now) } + } else if activeOveride.isPreset { // Because hard coded Hypo treatment isn't actually a preset + if let duration = os.cancelProfile() { + nightscoutManager.editOverride("📉", duration, activeOveride.date ?? Date.now) + } } else { let nsString = activeOveride.percentage.formatted() != "100" ? activeOveride.percentage .formatted() + " %" : "Custom" @@ -490,6 +498,8 @@ extension Home { guard let self = self else { return } if let data = self.provider.reasons() { self.iobData = data + self.carbData = data.map(\.cob).reduce(0, +) + self.iobs = data.map(\.iob).reduce(0, +) neg = data.filter({ $0.iob < 0 }).count * 5 let tdds = CoreDataStorage().fetchTDD(interval: DateFilter().tenDays) let yesterday = (tdds.first(where: { @@ -592,6 +602,7 @@ extension Home.StateModel: maxBolus = settingsManager.pumpSettings.maxBolus useInsulinBars = settingsManager.settings.useInsulinBars skipGlucoseChart = settingsManager.settings.skipGlucoseChart + displayDelta = settingsManager.settings.displayDelta setupGlucose() setupOverrideHistory() setupData() diff --git a/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift b/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift index 2208217a69..0ca6f0a066 100644 --- a/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift @@ -2,19 +2,13 @@ import SwiftUI struct CurrentGlucoseView: View { @Binding var recentGlucose: BloodGlucose? - @Binding var timerDate: Date @Binding var delta: Int? @Binding var units: GlucoseUnits @Binding var alarm: GlucoseAlarm? @Binding var lowGlucose: Decimal @Binding var highGlucose: Decimal @Binding var alwaysUseColors: Bool - - @State private var rotationDegrees: Double = 0.0 - - enum Config { - static let size: CGFloat = 100 - } + @Binding var displayDelta: Bool @Environment(\.colorScheme) var colorScheme @Environment(\.sizeCategory) private var fontSize @@ -26,17 +20,38 @@ struct CurrentGlucoseView: View { if units == .mmolL { formatter.minimumFractionDigits = 1 formatter.maximumFractionDigits = 1 + formatter.roundingMode = .halfUp } - formatter.roundingMode = .halfUp return formatter } + private var manualGlucoseFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + if units == .mmolL { + formatter.minimumFractionDigits = 1 + formatter.maximumFractionDigits = 1 + formatter.roundingMode = .ceiling + } + return formatter + } + + private var decimalString: String { + let formatter = NumberFormatter() + return formatter.decimalSeparator + } + private var deltaFormatter: NumberFormatter { let formatter = NumberFormatter() formatter.numberStyle = .decimal + if units == .mmolL { + formatter.minimumIntegerDigits = 0 + formatter.decimalSeparator = "." + } formatter.maximumFractionDigits = 1 - formatter.positivePrefix = " +" - formatter.negativePrefix = " -" + formatter.positivePrefix = "+" + formatter.negativePrefix = "-" return formatter } @@ -55,79 +70,120 @@ struct CurrentGlucoseView: View { } var body: some View { + glucoseView + .dynamicTypeSize(DynamicTypeSize.medium ... DynamicTypeSize.xLarge) + } + + var glucoseView: some View { ZStack { - VStack { - let offset: CGFloat = fontSize < .large ? 82 : (fontSize >= .large && fontSize < .extraExtraLarge) ? 87 : 92 - ZStack { - Text( - (recentGlucose?.glucose ?? 100) == 400 ? "HIGH" : recentGlucose?.glucose - .map { - glucoseFormatter - .string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)! } - ?? "--" - ) - .font(.glucoseFont) - .foregroundColor(alwaysUseColors ? colorOfGlucose : alarm == nil ? .primary : .loopRed) - .frame(maxWidth: .infinity, alignment: .center) - - HStack(spacing: 10) { - image - .font(.system(size: 25)) - VStack { - Text( - delta - .map { - deltaFormatter - .string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)! - } ?? "--" - ) - HStack { - let minutesAgo = -1 * (recentGlucose?.dateString.timeIntervalSinceNow ?? 0) / 60 - let text = timaAgoFormatter.string(for: Double(minutesAgo)) ?? "" - Text( - minutesAgo <= 1 ? "" : ( - text + " " + - NSLocalizedString("min", comment: "Short form for minutes") + " " - ) - ) - }.offset(x: 7, y: 0) - } - .font(.extraSmall).foregroundStyle(.secondary) - }.frame(maxWidth: .infinity, alignment: .center) - .offset(x: offset, y: 0) + if let recent = recentGlucose { + if displayDelta, let deltaInt = delta, !(units == .mmolL && abs(deltaInt) <= 1) { deltaView(deltaInt) } + VStack(spacing: 15) { + let formatter = recent.type == GlucoseType.manual.rawValue ? manualGlucoseFormatter : glucoseFormatter + if let string = recent.glucose.map({ + formatter + .string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber) ?? "" }) + { + glucoseText(string).asAny() + .background { glucoseDrop } + let minutesAgo = -1 * recent.dateString.timeIntervalSinceNow / 60 + let text = timaAgoFormatter.string(for: Double(minutesAgo)) ?? "" + Text( + minutesAgo <= 1 ? "Now" : + (text + " " + NSLocalizedString("min", comment: "Short form for minutes") + " ") + ) + .font(.caption) + .foregroundStyle(.secondary) + .offset(x: 2, y: fontSize >= .extraLarge ? -3 : 0) + } } - .dynamicTypeSize(DynamicTypeSize.medium ... DynamicTypeSize.xLarge) } } } - var image: some View { + private func deltaView(_ deltaInt: Int) -> some View { + ZStack { + let offset: CGFloat = 4 + let deltaConverted = units == .mmolL ? deltaInt.asMmolL : Decimal(deltaInt) + + Text(deltaFormatter.string(from: deltaConverted as NSNumber) ?? "") + .font(.caption) + .offset(x: -(offset / 2)) + .background { directionDrop } + .offset(x: offset, y: 10) + } + .dynamicTypeSize(DynamicTypeSize.medium ... DynamicTypeSize.large) + .frame(maxHeight: .infinity, alignment: .center).offset(x: 120, y: -7) + } + + private var adjustments: (degree: Double, x: CGFloat, y: CGFloat) { + let yOffset: CGFloat = 17 guard let direction = recentGlucose?.direction else { - return Image(systemName: "arrow.left.and.right") + return (90, 0, yOffset) } switch direction { case .doubleUp, .singleUp, .tripleUp: - return Image(systemName: "arrow.up") + return (0, 0, yOffset) case .fortyFiveUp: - return Image(systemName: "arrow.up.right") + return (45, 0, yOffset) case .flat: - return Image(systemName: "arrow.forward") + return (90, 0, yOffset) case .fortyFiveDown: - return Image(systemName: "arrow.down.forward") + return (135, 0, yOffset) case .doubleDown, .singleDown, .tripleDown: - return Image(systemName: "arrow.down") + return (180, 0, yOffset) case .none, .notComputable, .rateOutOfRange: - return Image(systemName: "arrow.left.and.right") + return (90, 0, yOffset) + } + } + + private func glucoseText(_ string: String) -> any View { + ZStack { + let decimal = string.components(separatedBy: decimalString) + if decimal.count > 1 { + HStack(spacing: 0) { + Text(decimal[0]).font(.glucoseFont) + Text(decimalString).font(.system(size: 28).weight(.semibold)).baselineOffset(-10) + Text(decimal[1]).font(.system(size: 28)).baselineOffset(-10) + } + .tracking(-1) + .offset(x: 0, y: 14) + .foregroundColor(alwaysUseColors ? colorOfGlucose : alarm == nil ? .primary : .loopRed) + } else { + Text(string) + .font(.glucoseFontMdDl.width(.condensed)) // .tracking(-2) + .foregroundColor(alwaysUseColors ? colorOfGlucose : alarm == nil ? .primary : .loopRed) + .offset(x: 0, y: 16) + } } } - var colorOfGlucose: Color { + private var glucoseDrop: some View { + let adjust = adjustments + let degree = adjustments.degree + return Image("glucoseDrops") + .resizable() + .frame(width: 140, height: 140).rotationEffect(.degrees(degree)) + .animation(.bouncy(duration: 1, extraBounce: 0.2), value: degree) + .offset(x: adjust.x, y: adjust.y) + .shadow(radius: 3) + } + + private var directionDrop: some View { + let degree = adjustments.degree + return Image("glucoseDrops") + .resizable() + .frame(width: 40, height: 40).rotationEffect(.degrees(degree)) + .animation(.bouncy(duration: 1, extraBounce: 0.2), value: degree) + } + + private var colorOfGlucose: Color { let whichGlucose = recentGlucose?.glucose ?? 0 guard lowGlucose < highGlucose else { return .primary } diff --git a/FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift b/FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift index bece30b613..6ed1ceaa09 100644 --- a/FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift @@ -25,32 +25,39 @@ struct LoopView: View { @Environment(\.sizeCategory) private var fontSize var body: some View { - VStack { - let multiplyForLargeFonts = fontSize > .extraLarge ? 1.2 : 1 + VStack(spacing: 2) { + let multiplyForLargeFonts = fontSize > .extraLarge ? 1.1 : 1 + + HStack(spacing: 0) { + Text("i").font(.system(size: 10, design: .rounded)).offset(y: 0.35) + Text("APS").font(.system(size: 12, design: .rounded)) + }.foregroundStyle(.secondary.opacity(0.5)) + LoopEllipse(stroke: color) - .frame(width: minutesAgo > 9 ? 70 * multiplyForLargeFonts : 60 * multiplyForLargeFonts, height: 27) + .frame(width: minutesAgo > 9 ? 60 * multiplyForLargeFonts : 60 * multiplyForLargeFonts, height: 27) .overlay { - let textColor: Color = .secondary HStack { ZStack { if closedLoop { if !isLooping, actualSuggestion?.timestamp != nil { - if minutesAgo > 1440 { - Text("--").font(.loopFont).foregroundColor(textColor).padding(.leading, 5) + if minutesAgo > 999 { + Text("--").font(.caption).padding(.leading, 5).foregroundColor(.secondary) } else { - let timeString = "\(minutesAgo) " + - NSLocalizedString("min", comment: "Minutes ago since last loop") - Text(timeString).font(.loopFont).foregroundColor(textColor) + let timeString = NSLocalizedString("m", comment: "Minutes ago since last loop") + HStack(spacing: 0) { + Text("\(minutesAgo) ") + Text(timeString).foregroundColor(.secondary) + }.font(.caption) } } if isLooping { ProgressView() } } else if !isLooping { - Text("Open").font(.loopFont) + Text("Open").font(.caption) } } - } + }.dynamicTypeSize(...DynamicTypeSize.xLarge) } } } diff --git a/FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift b/FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift index 0d8d268d76..a34c89bad0 100644 --- a/FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift @@ -72,6 +72,7 @@ struct PumpView: View { .frame(maxHeight: 15) .foregroundColor(batteryColor) .offset(x: 0, y: -4) + .shadow(radius: 2) .overlay { if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() { ClockOffset(mdtPump: true) @@ -85,6 +86,7 @@ struct PumpView: View { .frame(width: IAPSconfig.iconSize * 1.15, height: IAPSconfig.iconSize * 1.6) .foregroundColor(colorScheme == .dark ? .secondary : .white) .offset(x: 0, y: -5) + .shadow(radius: 2) .overlay { if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() { ClockOffset(mdtPump: false) diff --git a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift index 353ac5b28d..96a10fd0e5 100644 --- a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift +++ b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift @@ -14,13 +14,10 @@ extension Home { @State var showCancelAlert = false @State var showCancelTTAlert = false @State var triggerUpdate = false - @State var scrollOffset = CGFloat.zero @State var display = false - - @Namespace var scrollSpace - - let scrollAmount: CGFloat = 290 + @State var displayGlucose = false let buttonFont = Font.custom("TimeButtonFont", size: 14) + let viewPadding: CGFloat = 5 @Environment(\.managedObjectContext) var moc @Environment(\.colorScheme) var colorScheme @@ -94,13 +91,13 @@ extension Home { var glucoseView: some View { CurrentGlucoseView( recentGlucose: $state.recentGlucose, - timerDate: $state.timerDate, delta: $state.glucoseDelta, units: $state.units, alarm: $state.alarm, lowGlucose: $state.lowGlucose, highGlucose: $state.highGlucose, - alwaysUseColors: $state.alwaysUseColors + alwaysUseColors: $state.alwaysUseColors, + displayDelta: $state.displayDelta ) .onTapGesture { if state.alarm == nil { @@ -134,6 +131,7 @@ extension Home { state.setupPump = true } } + .offset(y: 1) } var loopView: some View { @@ -153,11 +151,12 @@ extension Home { impactHeavy.impactOccurred() state.runLoop() } + .offset(y: 10) } - var tempBasalString: String? { + var tempBasalString: String { guard let tempRate = state.tempRate else { - return nil + return "?" + NSLocalizedString(" U/hr", comment: "Unit per hour with space") } let rateString = numberFormatter.string(from: tempRate as NSNumber) ?? "0" var manualBasalString = "" @@ -185,7 +184,7 @@ extension Home { if state.pumpSuspended { Text("Pump suspended") .font(.extraSmall).bold().foregroundColor(.loopGray) - } else if let tempBasalString = tempBasalString { + } else { Text(tempBasalString) .font(.statusFont).bold() .foregroundColor(.insulin) @@ -207,28 +206,32 @@ extension Home { } ZStack { - if let eventualBG = state.eventualBG { - HStack { - Text("⇢").font(.statusFont).foregroundStyle(.secondary) + HStack { + Text("⇢").font(.statusFont).foregroundStyle(.secondary) - // Image(systemName: "arrow.forward") + if let eventualBG = state.eventualBG { Text( fetchedTargetFormatter.string( from: (state.units == .mmolL ? eventualBG.asMmolL : Decimal(eventualBG)) as NSNumber - )! + ) ?? "" ).font(.statusFont).foregroundColor(colorScheme == .dark ? .white : .black) - Text(state.units.rawValue).font(.system(size: 12)).foregroundStyle(.secondary) + } else { + Text("?").font(.statusFont).foregroundStyle(.secondary) } - .frame(maxWidth: .infinity, alignment: .trailing) - .padding(.trailing, 8) + Text(state.units.rawValue).font(.system(size: 12)).foregroundStyle(.secondary) } + .frame(maxWidth: .infinity, alignment: .trailing) + .padding(.trailing, 8) } - }.dynamicTypeSize(...DynamicTypeSize.xxLarge) + } + .dynamicTypeSize(...DynamicTypeSize.xxLarge) } var infoPanel: some View { - info - .frame(minHeight: 35, maxHeight: 35) + info.frame(height: 26) + .background { + InfoPanelBackground(colorScheme: colorScheme) + } } var mainChart: some View { @@ -278,102 +281,105 @@ extension Home { .frame(height: 50 + geo.safeAreaInsets.bottom) let isOverride = fetchedPercent.first?.enabled ?? false let isTarget = (state.tempTarget != nil) - HStack { - Button { state.showModal(for: .addCarbs(editMode: false, override: false)) } - label: { - ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) { - Image(systemName: "fork.knife") - .renderingMode(.template) - .font(.custom("Buttons", size: 24)) - .foregroundColor(colorScheme == .dark ? .loopYellow : .orange) - .padding(8) - .foregroundColor(.loopYellow) - if let carbsReq = state.carbsRequired { - Text(numberFormatter.string(from: carbsReq as NSNumber)!) - .font(.caption) - .foregroundColor(.white) - .padding(4) - .background(Capsule().fill(Color.red)) + VStack { + Divider() + HStack { + Button { state.showModal(for: .addCarbs(editMode: false, override: false)) } + label: { + ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) { + Image(systemName: "fork.knife") + .renderingMode(.template) + .font(.custom("Buttons", size: 24)) + .foregroundColor(colorScheme == .dark ? .loopYellow : .orange) + .padding(8) + .foregroundColor(.loopYellow) + if let carbsReq = state.carbsRequired { + Text(numberFormatter.string(from: carbsReq as NSNumber)!) + .font(.caption) + .foregroundColor(.white) + .padding(4) + .background(Capsule().fill(Color.red)) + } } + }.buttonStyle(.borderless) + Spacer() + Button { + state.showModal(for: .bolus( + waitForSuggestion: state.useCalc ? true : false, + fetch: false + )) } - }.buttonStyle(.borderless) - Spacer() - Button { - state.showModal(for: .bolus( - waitForSuggestion: state.useCalc ? true : false, - fetch: false - )) - } - label: { - Image(systemName: "syringe") - .renderingMode(.template) - .font(.custom("Buttons", size: 24)) - } - .buttonStyle(.borderless) - .foregroundColor(.insulin) - Spacer() - if state.allowManualTemp { - Button { state.showModal(for: .manualTempBasal) } label: { - Image("bolus1") + Image(systemName: "syringe") .renderingMode(.template) - .resizable() - .frame(width: IAPSconfig.buttonSize, height: IAPSconfig.buttonSize, alignment: .bottom) + .font(.custom("Buttons", size: 24)) } + .buttonStyle(.borderless) .foregroundColor(.insulin) Spacer() - } - ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) { - Image(systemName: isOverride ? "person.fill" : "person") - .symbolRenderingMode(.palette) - .font(.custom("Buttons", size: 28)) - .foregroundStyle(.purple) - .padding(8) - .background(isOverride ? .purple.opacity(0.15) : .clear) - .clipShape(RoundedRectangle(cornerRadius: 10)) - } - .onTapGesture { - if isOverride { - showCancelAlert.toggle() - } else { + if state.allowManualTemp { + Button { state.showModal(for: .manualTempBasal) } + label: { + Image("bolus1") + .renderingMode(.template) + .resizable() + .frame(width: IAPSconfig.buttonSize, height: IAPSconfig.buttonSize, alignment: .bottom) + } + .foregroundColor(.insulin) + Spacer() + } + ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) { + Image(systemName: isOverride ? "person.fill" : "person") + .symbolRenderingMode(.palette) + .font(.custom("Buttons", size: 28)) + .foregroundStyle(.purple) + .padding(8) + .background(isOverride ? .purple.opacity(0.15) : .clear) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } + .onTapGesture { + if isOverride { + showCancelAlert.toggle() + } else { + state.showModal(for: .overrideProfilesConfig) + } + } + .onLongPressGesture { state.showModal(for: .overrideProfilesConfig) } - } - .onLongPressGesture { - state.showModal(for: .overrideProfilesConfig) - } - if state.useTargetButton { - Spacer() - Image(systemName: "target") - .renderingMode(.template) - .font(.custom("Buttons", size: 24)) - .padding(8) - .foregroundColor(.loopGreen) - .background(isTarget ? .green.opacity(0.15) : .clear) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .onTapGesture { - if isTarget { - showCancelTTAlert.toggle() - } else { + if state.useTargetButton { + Spacer() + Image(systemName: "target") + .renderingMode(.template) + .font(.custom("Buttons", size: 24)) + .padding(8) + .foregroundColor(.loopGreen) + .background(isTarget ? .green.opacity(0.15) : .clear) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .onTapGesture { + if isTarget { + showCancelTTAlert.toggle() + } else { + state.showModal(for: .addTempTarget) + } + } + .onLongPressGesture { state.showModal(for: .addTempTarget) } - } - .onLongPressGesture { - state.showModal(for: .addTempTarget) - } - } - Spacer() - Button { state.showModal(for: .settings) } - label: { - Image(systemName: "gear") - .renderingMode(.template) - .font(.custom("Buttons", size: 24)) + } + Spacer() + Button { state.showModal(for: .settings) } + label: { + Image(systemName: "gear") + .renderingMode(.template) + .font(.custom("Buttons", size: 24)) + } + .buttonStyle(.borderless) + .foregroundColor(.gray) } - .buttonStyle(.borderless) - .foregroundColor(.gray) + .padding(.horizontal, state.allowManualTemp ? 5 : 24) + .padding(.bottom, geo.safeAreaInsets.bottom) } - .padding(.horizontal, state.allowManualTemp ? 5 : 24) - .padding(.bottom, geo.safeAreaInsets.bottom) } .dynamicTypeSize(...DynamicTypeSize.xxLarge) .confirmationDialog("Cancel Profile Override", isPresented: $showCancelAlert) { @@ -390,15 +396,12 @@ extension Home { } var chart: some View { - let ratio = state.timeSettings ? 1.61 : 1.44 - let ratio2 = state.timeSettings ? 1.65 : 1.51 + let ratio = state.timeSettings ? 1.73 : 1.56 + let ratio2 = state.timeSettings ? 1.77 : 1.63 - return addColouredBackground() + return addColouredBackground().shadow(radius: 3, y: 3) .overlay { - VStack(spacing: 0) { - infoPanel - mainChart - } + mainChart } .frame(minHeight: UIScreen.main.bounds.height / (fontSize < .extraExtraLarge ? ratio : ratio2)) } @@ -406,13 +409,15 @@ extension Home { var carbsAndInsulinView: some View { HStack { if let settings = state.settingsManager { - let opacity: CGFloat = colorScheme == .dark ? 0.2 : 0.6 + // A temporary ugly(?) workaround for displaying last real IOB and COB computation + let opacity: CGFloat = colorScheme == .dark ? 0.2 : 0.65 let materialOpacity: CGFloat = colorScheme == .dark ? 0.25 : 0.10 + // Carbs on Board HStack { let substance = Double(state.suggestion?.cob ?? 0) let max = max(Double(settings.preferences.maxCOB), 1) let fraction: Double = 1 - (substance / max) - let fill = CGFloat(min(Swift.max(fraction, 0.05), substance > 0 ? 0.92 : 0.97)) + let fill = CGFloat(min(Swift.max(fraction, 0.05), substance > 0 ? 0.92 : 1)) TestTube( opacity: opacity, amount: fill, @@ -422,17 +427,22 @@ extension Home { .frame(width: 12, height: 38) .offset(x: 0, y: -5) HStack(spacing: 0) { - Text( - numberFormatter.string(from: (state.suggestion?.cob ?? 0) as NSNumber) ?? "0" - ).font(.statusFont).bold() + if let loop = state.suggestion, let cob = loop.cob { + Text(numberFormatter.string(from: cob as NSNumber) ?? "0") + .font(.statusFont).bold() + // Display last loop, unless very old + } else { + Text("?").font(.statusFont).bold() + } Text(NSLocalizedString(" g", comment: "gram of carbs")).font(.statusFont).foregroundStyle(.secondary) }.offset(x: 0, y: 5) } + // Insulin on Board HStack { let substance = Double(state.suggestion?.iob ?? 0) let max = max(Double(settings.preferences.maxIOB), 1) - let fraction: Double = 1 - (substance / max) - let fill = CGFloat(min(Swift.max(fraction, 0.05), substance > 0 ? 0.92 : 0.98)) + let fraction: Double = 1 - abs(substance) / max + let fill = CGFloat(min(Swift.max(fraction, 0.05), 1)) TestTube( opacity: opacity, amount: fill, @@ -442,14 +452,19 @@ extension Home { .frame(width: 12, height: 38) .offset(x: 0, y: -5) HStack(spacing: 0) { - Text( - numberFormatter.string(from: (state.suggestion?.iob ?? 0) as NSNumber) ?? "0" - ).font(.statusFont).bold() + if let loop = state.suggestion, let iob = loop.iob { + Text( + numberFormatter.string(from: iob as NSNumber) ?? "0" + ).font(.statusFont).bold() + } else { + Text("?").font(.statusFont).bold() + } Text(NSLocalizedString(" U", comment: "Insulin unit")).font(.statusFont).foregroundStyle(.secondary) }.offset(x: 0, y: 5) } } } + .offset(y: 5) } var preview: some View { @@ -468,7 +483,7 @@ extension Home { var activeIOBView: some View { addBackground() - .frame(minHeight: 430) + .frame(minHeight: 405) .overlay { ActiveIOBView( data: $state.iobData, @@ -488,7 +503,7 @@ extension Home { var activeCOBView: some View { addBackground() - .frame(minHeight: 230) + .frame(minHeight: 190) .overlay { ActiveCOBView(data: $state.iobData) } @@ -499,7 +514,7 @@ extension Home { var loopPreview: some View { addBackground() - .frame(minHeight: 190) + .frame(minHeight: 160) .overlay { LoopsView(loopStatistics: $state.loopStatistics) } @@ -528,7 +543,7 @@ extension Home { Text(name).font(.statusFont).foregroundStyle(.secondary) } } - } + } else { Text("📉") } // Hypo Treatment is not actually a preset } else if override.percentage != 100 { Text(override.percentage.formatted() + " %").font(.statusFont).foregroundStyle(.secondary) } else if override.smbIsOff, !override.smbIsAlwaysOff { @@ -574,47 +589,46 @@ extension Home { } } - @ViewBuilder private func headerView(_ geo: GeometryProxy) -> some View { + @ViewBuilder private func headerView(_ geo: GeometryProxy, extra: CGFloat) -> some View { + let scrolling: Bool = extra > 0 + let height: CGFloat = scrolling ? 170 : 170 addHeaderBackground() .frame( - maxHeight: fontSize < .extraExtraLarge ? 125 + geo.safeAreaInsets.top : 135 + geo - .safeAreaInsets.top + height: fontSize < .extraExtraLarge ? height + geo.safeAreaInsets.top + extra : height + 10 + geo + .safeAreaInsets.top + extra ) + .clipShape(Rectangle()) .overlay { VStack { ZStack { - glucoseView.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top).padding(.top, 10) + glucoseView.frame(maxHeight: .infinity, alignment: .center).offset(y: -5) + loopView.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .padding(.leading, 32).padding(.top, 20) HStack { carbsAndInsulinView .frame(maxHeight: .infinity, alignment: .bottom) Spacer() - loopView.frame(maxHeight: .infinity, alignment: .bottom).padding(.bottom, 3) - .offset(x: -2, y: 0) // To do: Remove all offsets, if possible. - Spacer() pumpView .frame(maxHeight: .infinity, alignment: .bottom) - .padding(.bottom, 2) } .dynamicTypeSize(...DynamicTypeSize.xLarge) .padding(.horizontal, 10) } - }.padding(.top, geo.safeAreaInsets.top).padding(.bottom, 10) + // Small glucose View, past 24 hours. + if displayGlucose { glucoseHeaderView() } + + if !scrolling { + infoPanel + } + }.padding(.top, geo.safeAreaInsets.top) } - .clipShape(Rectangle()) } @ViewBuilder private func glucoseHeaderView() -> some View { - addHeaderBackground() - .frame(maxHeight: 90) - .overlay { - VStack { - ZStack { - glucosePreview.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .dynamicTypeSize(...DynamicTypeSize.medium) - } - } - } - .clipShape(Rectangle()) + ZStack { + glucosePreview + .dynamicTypeSize(...DynamicTypeSize.medium) + } } var glucosePreview: some View { @@ -636,7 +650,7 @@ extension Home { (($0.glucose ?? 0) > veryHigh || Decimal($0.glucose ?? 0) < low) ? Color(.red) : Decimal($0.glucose ?? 0) > high ? Color(.yellow) : Color(.darkGreen) ) - .symbolSize(7) + .symbolSize(5) } .chartXAxis { AxisMarks(values: .stride(by: .hour, count: 2)) { _ in @@ -656,11 +670,10 @@ extension Home { .chartXScale( domain: Date.now.addingTimeInterval(-1.days.timeInterval) ... Date.now ) - .frame(maxHeight: 70) + .frame(height: 70) .padding(.leading, 30) .padding(.trailing, 32) - .padding(.top, 10) - .padding(.bottom, 10) + .padding(.top, 15) } var timeSetting: some View { @@ -682,40 +695,44 @@ extension Home { var body: some View { GeometryReader { geo in VStack(spacing: 0) { - headerView(geo) - if !state.skipGlucoseChart, scrollOffset > scrollAmount { - glucoseHeaderView() - .transition(.move(edge: .top)) - } - + // Header View + headerView(geo, extra: (displayGlucose && !state.skipGlucoseChart) ? 59 : 0) ScrollView { - ScrollViewReader { _ in - LazyVStack { - chart - if state.timeSettings { timeSetting } - preview.padding(.top, state.timeSettings ? 5 : 15) - loopPreview.padding(.top, 15) - if state.iobData.count > 5 { - activeCOBView.padding(.top, 15) - activeIOBView.padding(.top, 15) - } + VStack { + // Main Chart + chart + // Adjust hours visible (X-Axis) + if state.timeSettings, !displayGlucose { timeSetting } + // TIR Chart + preview.padding(.top, (state.timeSettings && !displayGlucose) ? 5 : 15) + // Loops Chart + loopPreview.padding(.vertical, 15) + // COB Chart + if state.carbData > 0 { + activeCOBView } - .background(GeometryReader { geo in - let offset = -geo.frame(in: .named(scrollSpace)).minY + // IOB Chart + if state.iobs > 0 { + activeIOBView.padding(.top, state.carbData > 0 ? viewPadding : 0) + } + }.background { + // Track vertical scroll + GeometryReader { proxy in + let scrollPosition = proxy.frame(in: .named("HomeScrollView")).minY + let yThreshold: CGFloat = state.timeSettings ? -500 : -560 Color.clear - .preference( - key: ScrollViewOffsetPreferenceKey.self, - value: offset - ) - }) - } - } - .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in - scrollOffset = value - if !state.skipGlucoseChart, scrollOffset > scrollAmount { - display.toggle() + .onChange(of: scrollPosition) { y in + if y < yThreshold, state.iobs > 0 || state.carbData > 0, !state.skipGlucoseChart { + withAnimation(.easeOut(duration: 0.3)) { displayGlucose = true } + } else { + withAnimation(.easeOut(duration: 0.4)) { displayGlucose = false } + } + } + } } - } + + }.coordinateSpace(name: "HomeScrollView") + // Buttons buttonPanel(geo) } .background( @@ -775,7 +792,7 @@ extension Home { Text("No sugestion found").font(.suggestionHeadline).foregroundColor(.white) } if let errorMessage = state.errorMessage, let date = state.errorDate { - Text(NSLocalizedString("Error at", comment: "") + " " + dateFormatter.string(from: date)) + Text(NSLocalizedString("Status at", comment: "") + " " + dateFormatter.string(from: date)) .foregroundColor(.white) .font(.suggestionError) .padding(.bottom, 4) diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveCOBView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveCOBView.swift index db17ddcd4b..71f69ccae5 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveCOBView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveCOBView.swift @@ -14,10 +14,9 @@ struct ActiveCOBView: View { var body: some View { VStack { - Text("Active Carbohydrates").font(.previewHeadline).padding(.top, 20) - cobView().frame(maxHeight: 130).padding(.vertical, 10).padding(.horizontal, 20) - .padding(.bottom, 10) - }.dynamicTypeSize(...DynamicTypeSize.medium) + Text("Active Carbohydrates").font(.previewHeadline).padding(.top, 20).padding(.bottom, 15) + cobView().frame(maxHeight: 130).padding(.bottom, 10).padding(.horizontal, 20) + }.dynamicTypeSize(...DynamicTypeSize.xLarge) } @ViewBuilder private func cobView() -> some View { @@ -27,25 +26,25 @@ struct ActiveCOBView: View { AreaMark( x: .value("Time", $0.date), y: .value("COB", $0.cob) - ).foregroundStyle(Color(.loopYellow)) - } - .chartYAxis { - AxisMarks(values: .automatic(desiredCount: 3)) - } - .chartXAxis { - AxisMarks(values: .stride(by: .hour, count: 2)) { _ in - AxisValueLabel( - format: .dateTime.hour(.defaultDigits(amPM: .omitted)) - .locale(Locale(identifier: "sv")) // Force 24h - ) - AxisGridLine() + ).foregroundStyle(Color(.loopYellow).gradient).opacity(0.8) } + .chartYAxis { + AxisMarks(values: .automatic(desiredCount: 3)) } - } - .chartYScale( - domain: 0 ... maximum - ) - .chartXScale( - domain: Date.now.addingTimeInterval(-1.days.timeInterval) ... Date.now - ) + .chartXAxis { + AxisMarks(values: .stride(by: .hour, count: 2)) { _ in + AxisValueLabel( + format: .dateTime.hour(.defaultDigits(amPM: .omitted)) + .locale(Locale(identifier: "sv")) // 24h format + ) + AxisGridLine() + } + } + .chartYScale( + domain: 0 ... maximum + ) + .chartXScale( + domain: Date.now.addingTimeInterval(-1.days.timeInterval) ... Date.now + ) + .chartLegend(.hidden) } } diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift index 892d15b6c6..8ed713c4f1 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/ActiveIOBView.swift @@ -29,23 +29,69 @@ struct ActiveIOBView: View { var body: some View { VStack { - Text("Active Insulin").font(.previewHeadline).padding(.top, 20) + Text("Active Insulin").font(.previewHeadline).padding(.top, 20).padding(.bottom, 15) iobView().frame(maxHeight: 130).padding(.horizontal, 20) - sumView().frame(maxHeight: 250).padding(.vertical, 30) + sumView().frame(maxHeight: 250).padding(.top, 20).padding(.bottom, 10) }.dynamicTypeSize(...DynamicTypeSize.xLarge) } @ViewBuilder private func iobView() -> some View { - let minimum = data.map(\.iob).min() ?? 0 + // Data + let negIOBData = negIOBdata(data) + // Domain + let minimum = min(data.map(\.iob).min() ?? 0, negIOBData.map(\.iob).min() ?? 0) let minimumRange = min(0, minimum * 1.3) let maximum = (data.map(\.iob).max() ?? 0) * 1.1 - Chart(data) { - AreaMark( - x: .value("Time", $0.date), - y: .value("IOB", $0.iob) - ).foregroundStyle(Color(.insulin)) + Chart { + ForEach(data) { item in + LineMark( + x: .value("Time", item.date), + y: .value("IOB", item.iob) + ).foregroundStyle(by: .value("Time", "Line IOB > 0")) + .lineStyle(StrokeStyle(lineWidth: 0.8)) + + AreaMark( + x: .value("Time", item.date), + y: .value("IOB", item.iob) + ).foregroundStyle(by: .value("Time", "IOB > 0")) + } + ForEach(negIOBData) { item in + AreaMark( + x: .value("Time", item.date), + yStart: .value("IOB", 0), + yEnd: .value("IOB", item.iob) + ).foregroundStyle(by: .value("Time", "IOB < 0")) + } } + .chartForegroundStyleScale( + [ + "IOB > 0": LinearGradient( + gradient: Gradient(colors: [ + Color.insulin.opacity(1), + Color.insulin.opacity(0.4) + ]), + startPoint: .top, + endPoint: .bottom + ), + "IOB < 0": LinearGradient( + gradient: Gradient(colors: [ + Color.red.opacity(1), + Color.red.opacity(1) + ]), + startPoint: .bottom, + endPoint: .top + ), + "Line IOB > 0": LinearGradient( + gradient: Gradient(colors: [ + Color.insulin.opacity(1), + Color.insulin.opacity(1) + ]), + startPoint: .top, + endPoint: .bottom + ) + ] + ) .chartXAxis { AxisMarks(values: .stride(by: .hour, count: 2)) { _ in AxisValueLabel( @@ -64,6 +110,7 @@ struct ActiveIOBView: View { .chartXScale( domain: Date.now.addingTimeInterval(-1.days.timeInterval) ... Date.now ) + .chartLegend(.hidden) } @ViewBuilder private func sumView() -> some View { @@ -93,7 +140,7 @@ struct ActiveIOBView: View { color: Color(.clear) ), BolusSummary( - variable: NSLocalizedString("Average Insulin past 24h", comment: ""), + variable: NSLocalizedString("Average Insulin 10 days", comment: ""), formula: NSLocalizedString(" U", comment: ""), insulin: tddActualAverage, color: .secondary @@ -158,4 +205,21 @@ struct ActiveIOBView: View { } return data } + + private func negIOBdata(_ data: [IOBData]) -> [IOBData] { + var array = [IOBData]() + var previous = data.first + for item in data { + if item.iob < 0 { + if previous?.iob ?? 0 >= 0 { + array.append(IOBData(date: previous?.date ?? .distantPast, iob: 0, cob: 0)) + } + array.append(IOBData(date: item.date, iob: item.iob, cob: 0)) + } else if previous?.iob ?? 0 < 0 { + array.append(IOBData(date: item.date, iob: 0, cob: 0)) + } + previous = item + } + return array + } } diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/LoopsView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/LoopsView.swift index efa6629a4f..29793c03a9 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/LoopsView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/LoopsView.swift @@ -12,7 +12,7 @@ struct LoopsView: View { let readings = loopStatistics.1 let percentage = loopStatistics.2 - Text(NSLocalizedString("Loops", comment: "") + " / " + NSLocalizedString("Readings", comment: "")) + Text(NSLocalizedString("Loops", comment: "")) .padding(.bottom, 10).font(.previewHeadline) loopChart(percentage: percentage) @@ -32,19 +32,45 @@ struct LoopsView: View { Text("\(loops)") }.font(.loopFont) } - .padding(.top, 20) - .padding(.bottom, 15) .dynamicTypeSize(...DynamicTypeSize.xLarge) } func loopChart(percentage: Double) -> some View { VStack { Chart { + // Background chart 100 % + if percentage < 100 { + BarMark( + xStart: .value("LoopPercentage", percentage - 4), + xEnd: .value("Full Bar", 100) + ) + .foregroundStyle( + Color(.gray).opacity(0.3) + ) + .clipShape( + UnevenRoundedRectangle( + topLeadingRadius: 0, + bottomLeadingRadius: 0, + bottomTrailingRadius: 4, + topTrailingRadius: 4 + ) + ) + } + + // Loops per readings chart BarMark( x: .value("LoopPercentage", percentage) ) .foregroundStyle( percentage >= 90 ? Color(.darkGreen) : percentage >= 75 ? .orange : .red + ).opacity(1) + .clipShape( + UnevenRoundedRectangle( + topLeadingRadius: 4, + bottomLeadingRadius: 4, + bottomTrailingRadius: 4, + topTrailingRadius: 4 + ) ) .clipShape( UnevenRoundedRectangle( diff --git a/FreeAPS/Sources/Modules/Home/View/Previews/TIRView.swift b/FreeAPS/Sources/Modules/Home/View/Previews/TIRView.swift index af2ddeb8d4..6078bb9e95 100644 --- a/FreeAPS/Sources/Modules/Home/View/Previews/TIRView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Previews/TIRView.swift @@ -17,10 +17,134 @@ struct PreviewChart: View { } var body: some View { - let fetched = previewTir() + VStack { + let padding: CGFloat = 40 + // Prepare the chart data + let data = prepareData() + HStack { + Text("Today") + }.padding(.bottom, 15).font(.previewHeadline) + + HStack { + Chart(data) { item in + BarMark( + x: .value("TIR", item.type), + y: .value("Percentage", item.percentage), + width: .fixed(65) + ) + .foregroundStyle(by: .value("Group", item.group)) + .clipShape( + UnevenRoundedRectangle( + topLeadingRadius: (item.last || item.percentage == 100) ? 4 : 0, + bottomLeadingRadius: (item.first || item.percentage == 100) ? 4 : 0, + bottomTrailingRadius: (item.first || item.percentage == 100) ? 4 : 0, + topTrailingRadius: (item.last || item.percentage == 100) ? 4 : 0 + ) + ) + } + .chartForegroundStyleScale([ + NSLocalizedString( + "Low", + comment: "" + ): .red, + NSLocalizedString("In Range", comment: ""): .darkGreen, + NSLocalizedString( + "High", + comment: "" + ): .yellow, + NSLocalizedString( + "Very High", + comment: "" + ): .red, + NSLocalizedString( + "Very Low", + comment: "" + ): .darkRed, + "Separator": colorScheme == .dark ? .black : .white + ]) + .chartXAxis(.hidden) + .chartYAxis(.hidden) + .chartLegend(.hidden) + .padding(.bottom, 15) + .padding(.leading, padding) + .frame(maxWidth: (UIScreen.main.bounds.width / 5) + padding) + + sumView(data).offset(x: 0, y: -7) + } + + }.frame(maxHeight: 180) + .padding(.top, 20) + .dynamicTypeSize(...DynamicTypeSize.xLarge) + } + + @ViewBuilder private func sumView(_ data: [TIRinPercent]) -> some View { + let entries = data.reversed().filter { $0.group != "Separator" } + let padding: CGFloat = entries.count == 5 ? 4 : 35 / CGFloat(entries.count) + Grid { + ForEach(entries) { entry in + if entry.group != "Separator" { + GridRow(alignment: .firstTextBaseline) { + if entry.percentage != 0 { + HStack { + Text((tirFormatter.string(for: entry.percentage) ?? "") + "%") + Text(entry.group) + }.font( + entry.group == NSLocalizedString("In Range", comment: "") ? .previewHeadline : .previewSmall + ) + .foregroundStyle( + entry + .group == NSLocalizedString("In Range", comment: "") ? .primary : .secondary + ) + .padding( + .bottom, + (entries.count > 1 && entry.group != entries[entries.count - 1].group) ? padding : 0 + ) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + } + } + } + .dynamicTypeSize(...DynamicTypeSize.medium) + } + + private func previewTir() -> [(double: Double, string: String)] { + let hypoLimit = Int(lowLimit) + let hyperLimit = Int(highLimit) + let glucose = readings + 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 + var hyperPercentage = round(Double(hyperReadings) / Double(totalReadings) * 100) + let hypoArray = glucose.filter({ $0.glucose <= hypoLimit }) + let hypoReadings = hypoArray.compactMap({ each in each.glucose as Int16 }).count + var hypoPercentage = round(Double(hypoReadings) / Double(totalReadings) * 100) + let veryHighArray = glucose.filter({ $0.glucose > 197 }) + let veryHighReadings = veryHighArray.compactMap({ each in each.glucose as Int16 }).count + let veryHighPercentage = round(Double(veryHighReadings) / Double(totalReadings) * 100) + let veryLowArray = glucose.filter({ $0.glucose < 60 }) + let veryLowReadings = veryLowArray.compactMap({ each in each.glucose as Int16 }).count + let veryLowPercentage = round(Double(veryLowReadings) / Double(totalReadings) * 100) - let separator: Decimal = 3 + hypoPercentage -= veryLowPercentage + hyperPercentage -= veryHighPercentage + + let tir = round(100 - (hypoPercentage + hyperPercentage + veryHighPercentage + veryLowPercentage)) + var array: [(double: Double, string: String)] = [] + array.append((double: hypoPercentage, string: "Low")) + array.append((double: tir, string: "NormaL")) + array.append((double: hyperPercentage, string: "High")) + array.append((double: veryHighPercentage, string: "Very High")) + array.append((double: veryLowPercentage, string: "Very Low")) + + return array + } + + private func prepareData() -> [TIRinPercent] { + let fetched = previewTir() + let separator: Double = 2 var data: [TIRinPercent] = [ TIRinPercent( type: "TIR", @@ -28,7 +152,7 @@ struct PreviewChart: View { "Very Low", comment: "" ), - percentage: fetched[4].decimal, + percentage: fetched[4].double, id: UUID(), offset: -5, first: true, @@ -49,7 +173,7 @@ struct PreviewChart: View { "Low", comment: "" ), - percentage: fetched[0].decimal, + percentage: fetched[0].double, id: UUID(), offset: -10, first: false, @@ -67,7 +191,7 @@ struct PreviewChart: View { TIRinPercent( type: "TIR", group: NSLocalizedString("In Range", comment: ""), - percentage: fetched[1].decimal, + percentage: fetched[1].double, id: UUID(), offset: 0, first: false, @@ -88,7 +212,7 @@ struct PreviewChart: View { "High", comment: "" ), - percentage: fetched[2].decimal, + percentage: fetched[2].double, id: UUID(), offset: 10, first: false, @@ -109,7 +233,7 @@ struct PreviewChart: View { "Very High", comment: "" ), - percentage: fetched[3].decimal, + percentage: fetched[3].double, id: UUID(), offset: 5, first: false, @@ -117,175 +241,6 @@ struct PreviewChart: View { ) ] - // Preapre the data array - data = prepareData(data_: data) - - return VStack { - Text("Time In Range").padding(.bottom, 10).font(.previewHeadline) - - Chart(data) { item in - BarMark( - x: .value("TIR", item.type), - y: .value("Percentage", item.percentage), - width: .fixed(60) - ) - .foregroundStyle(by: .value("Group", item.group)) - .clipShape( - UnevenRoundedRectangle( - topLeadingRadius: (item.last || item.percentage == 100) ? 4 : 0, - bottomLeadingRadius: (item.first || item.percentage == 100) ? 4 : 0, - bottomTrailingRadius: (item.first || item.percentage == 100) ? 4 : 0, - topTrailingRadius: (item.last || item.percentage == 100) ? 4 : 0 - ) - ) - .annotation(position: .trailing) { - if item.group == NSLocalizedString("In Range", comment: ""), item.percentage > 0 { - HStack { - if item.percentage < 1 { - Text("< 1%") - } else { - Text((tirFormatter.string(from: item.percentage as NSNumber) ?? "") + "%") - } - Text(item.group) - }.font(.previewNormal) - .padding(.leading, 10) - } else if item.group == NSLocalizedString( - "Low", - comment: "" - ), item.percentage > 0.0 { - HStack { - if item.percentage < 1 { - Text("< 1%") - } else { - Text((tirFormatter.string(from: item.percentage as NSNumber) ?? "") + "%") - } - Text(item.group) - } - .offset(x: 0, y: item.offset) - .font(.loopFont) - .padding(.leading, 10) - } else if item.group == NSLocalizedString( - "High", - comment: "" - ), item.percentage > 0 { - HStack { - if item.percentage < 1 { - Text("< 1%") - } else { - Text((tirFormatter.string(from: item.percentage as NSNumber) ?? "") + "%") - } - Text(item.group) - } - .offset(x: 0, y: item.offset) - .font(.loopFont) - .padding(.leading, 10) - } else if item.group == NSLocalizedString( - "Very High", - comment: "" - ), item.percentage > 0 { - HStack { - if item.percentage < 1 { - Text("< 1%") - } else { - Text((tirFormatter.string(from: item.percentage as NSNumber) ?? "") + "%") - } - Text(item.group) - } - .offset(x: 0, y: item.offset) - .font(.loopFont) - .padding(.leading, 10) - } else if item.group == NSLocalizedString( - "Very Low", - comment: "" - ), item.percentage > 0 { - HStack { - if item.percentage < 1 { - Text("< 1%") - } else { - Text((tirFormatter.string(from: item.percentage as NSNumber) ?? "") + "%") - } - Text(item.group) - } - .offset(x: 0, y: item.offset) - .font(.loopFont) - .padding(.leading, 10) - } - } - } - .chartForegroundStyleScale([ - NSLocalizedString( - "Low", - comment: "" - ): .red, - NSLocalizedString("In Range", comment: ""): .darkGreen, - NSLocalizedString( - "High", - comment: "" - ): .yellow, - NSLocalizedString( - "Very High", - comment: "" - ): .red, - NSLocalizedString( - "Very Low", - comment: "" - ): .darkRed, - "Separator": colorScheme == .dark ? .black : .white - ]) - .chartXAxis(.hidden) - .chartYAxis(.hidden) - .chartLegend(.hidden) - .padding(.bottom, 15) - .frame(maxWidth: UIScreen.main.bounds.width / 5) - .offset(x: -UIScreen.main.bounds.width / 5, y: 0) - }.frame(maxHeight: 200) - .padding(.top, 20) - .dynamicTypeSize(...DynamicTypeSize.xLarge) - } - - private func previewTir() -> [(decimal: Decimal, string: String)] { - let hypoLimit = Int(lowLimit) - let hyperLimit = Int(highLimit) - - let glucose = readings - - 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 - var hyperPercentage = Double(hyperReadings) / Double(totalReadings) * 100 - - let hypoArray = glucose.filter({ $0.glucose <= hypoLimit }) - let hypoReadings = hypoArray.compactMap({ each in each.glucose as Int16 }).count - var hypoPercentage = Double(hypoReadings) / Double(totalReadings) * 100 - - let veryHighArray = glucose.filter({ $0.glucose > 197 }) - let veryHighReadings = veryHighArray.compactMap({ each in each.glucose as Int16 }).count - let veryHighPercentage = Double(veryHighReadings) / Double(totalReadings) * 100 - - let veryLowArray = glucose.filter({ $0.glucose < 60 }) - let veryLowReadings = veryLowArray.compactMap({ each in each.glucose as Int16 }).count - let veryLowPercentage = Double(veryLowReadings) / Double(totalReadings) * 100 - - hypoPercentage -= veryLowPercentage - hyperPercentage -= veryHighPercentage - - let tir = 100 - (hypoPercentage + hyperPercentage + veryHighPercentage + veryLowPercentage) - - 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")) - array.append((decimal: Decimal(veryHighPercentage), string: "Very High")) - array.append((decimal: Decimal(veryLowPercentage), string: "Very Low")) - - return array - } - - private func prepareData(data_: [TIRinPercent]) -> [TIRinPercent] { - var data = data_ - // Remove separators when needed for index in 0 ..< data.count - 2 { if index < data.count - 1 { @@ -333,7 +288,7 @@ struct PreviewChart: View { struct TIRinPercent: Identifiable { let type: String let group: String - let percentage: Decimal + let percentage: Double let id: UUID let offset: CGFloat var first: Bool diff --git a/FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift b/FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift index e98db2291a..fb9fcf4990 100644 --- a/FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift +++ b/FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift @@ -57,7 +57,7 @@ extension OverrideProfilesConfig { ns.editOverride(preset, duration, last?.date ?? Date.now) } else if let duration = OverrideStorage().cancelProfile() { let nsString = active.percentage.formatted() != "100" ? active.percentage - .formatted() + " %" : "Custom" + .formatted() + " %" : active.isPreset ? "📉" : "Custom" ns.editOverride(nsString, duration, last?.date ?? Date.now) } } @@ -162,7 +162,7 @@ extension OverrideProfilesConfig { let lastPreset = OverrideStorage().isPresetName() if let alreadyActive = last, alreadyActive.enabled, let duration = OverrideStorage().cancelProfile() { ns.editOverride( - (last?.isPreset ?? false) ? (lastPreset ?? "Override") : "Custom", + (last?.isPreset ?? false) ? (lastPreset ?? "📉") : "Custom", duration, alreadyActive.date ?? Date.now ) diff --git a/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift b/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift index bf418665a9..7cdff99272 100644 --- a/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift +++ b/FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift @@ -9,11 +9,13 @@ extension PreferencesEditor { @Published var sections: [FieldSection] = [] @Published var useAlternativeBolusCalc: Bool = false @Published var units: GlucoseUnits = .mmolL + @Published var maxCarbs: Decimal = 200 override func subscribe() { preferences = provider.preferences units = settingsManager.settings.units + subscribeSetting(\.maxCarbs, on: $maxCarbs) { maxCarbs = $0 } subscribeSetting(\.units, on: $unitsIndex.map { $0 == 0 ? GlucoseUnits.mgdL : .mmolL }) { unitsIndex = $0 == .mgdL ? 0 : 1 } didSet: { [weak self] _ in diff --git a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift index 0b25db6fad..b97005b2b3 100644 --- a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift +++ b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift @@ -71,6 +71,11 @@ extension PreferencesEditor { } } } + + // Exceptions. Below a FreeAPS setting added. + if field.displayName == NSLocalizedString("Max COB", comment: "Max COB") { + maxCarbs + } } } } @@ -112,5 +117,27 @@ extension PreferencesEditor { ) } } + + var maxCarbs: some View { + HStack { + ZStack { + Button("", action: { + infoButtonPressed = InfoText( + description: NSLocalizedString( + "Maximum amount of carbs (g) you can add each entry", + comment: "Max carbs description" + ), + oref0Variable: NSLocalizedString("Max Carbs", comment: "Max setting") + ) + }) + Text("Max Carbs") + } + DecimalTextField( + "0", + value: self.$state.maxCarbs, + formatter: formatter + ) + } + } } } diff --git a/FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorStateModel.swift b/FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorStateModel.swift index 1fb2a84c87..ecd77e0f42 100644 --- a/FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorStateModel.swift +++ b/FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorStateModel.swift @@ -5,8 +5,6 @@ extension PumpSettingsEditor { @Published var maxBasal: Decimal = 0.0 @Published var maxBolus: Decimal = 0.0 @Published var dia: Decimal = 0.0 - @Published var maxCarbs: Decimal = 1000 - @Published var syncInProgress = false override func subscribe() { @@ -14,7 +12,6 @@ extension PumpSettingsEditor { maxBasal = settings.maxBasal maxBolus = settings.maxBolus dia = settings.insulinActionCurve - subscribeSetting(\.maxCarbs, on: $maxCarbs) { maxCarbs = $0 } } func save() { diff --git a/FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift b/FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift index 4611097c4c..5bf0884898 100644 --- a/FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift +++ b/FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift @@ -23,10 +23,6 @@ extension PumpSettingsEditor { Text("Max Bolus") DecimalTextField("U", value: $state.maxBolus, formatter: formatter) } - HStack { - Text("Max Carbs") - DecimalTextField("g", value: $state.maxCarbs, formatter: formatter) - } } Section(header: Text("Duration of Insulin Action")) { diff --git a/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift b/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift index ff19e36bd3..b069218fb6 100644 --- a/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift +++ b/FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift @@ -94,14 +94,15 @@ extension Settings { .frame(maxWidth: .infinity, alignment: .trailing) .buttonStyle(.borderedProminent) } - /* - HStack { - Text("Delete All NS Overrides") - Button("Delete") { state.deleteOverrides() } - .frame(maxWidth: .infinity, alignment: .trailing) - .buttonStyle(.borderedProminent) - .tint(.red) - }*/ + + // Test code + HStack { + Text("Delete All NS Overrides") + Button("Delete") { state.deleteOverrides() } + .frame(maxWidth: .infinity, alignment: .trailing) + .buttonStyle(.borderedProminent) + .tint(.red) + } HStack { Toggle("Ignore flat CGM readings", isOn: $state.disableCGMError) diff --git a/FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift b/FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift index c8f15b3aa6..5ed218ea1f 100644 --- a/FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift +++ b/FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift @@ -18,6 +18,8 @@ extension StatConfig { @Published var minimumSMB: Decimal = 0.3 @Published var useInsulinBars: Bool = false @Published var skipGlucoseChart: Bool = false + @Published var disableHypoTreatment: Bool = false + @Published var displayDelta: Bool = false var units: GlucoseUnits = .mmolL @@ -37,6 +39,8 @@ extension StatConfig { subscribeSetting(\.skipBolusScreenAfterCarbs, on: $skipBolusScreenAfterCarbs) { skipBolusScreenAfterCarbs = $0 } subscribeSetting(\.oneDimensionalGraph, on: $oneDimensionalGraph) { oneDimensionalGraph = $0 } subscribeSetting(\.useInsulinBars, on: $useInsulinBars) { useInsulinBars = $0 } + subscribeSetting(\.disableHypoTreatment, on: $disableHypoTreatment) { disableHypoTreatment = $0 } + subscribeSetting(\.displayDelta, on: $displayDelta) { displayDelta = $0 } subscribeSetting(\.low, on: $low, initial: { let value = max(min($0, 90), 40) diff --git a/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift b/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift index c68125b5d7..eaf09bafa2 100644 --- a/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift +++ b/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift @@ -63,6 +63,7 @@ extension StatConfig { Section { Toggle("Never display the small glucose chart when scrolling", isOn: $state.skipGlucoseChart) Toggle("Always Color Glucose Value (green, yellow etc)", isOn: $state.alwaysUseColors) + Toggle("Display Glucose Delta", isOn: $state.displayDelta) } header: { Text("Header settings") } footer: { Text("Normally glucose is colored red only when over or under your notification limits for high/low") } @@ -86,6 +87,7 @@ extension StatConfig { Section { Toggle("Skip Bolus screen after carbs", isOn: $state.skipBolusScreenAfterCarbs) Toggle("Display and allow Fat and Protein entries", isOn: $state.useFPUconversion) + Toggle("Disable Hypo Treatments", isOn: $state.disableHypoTreatment) } header: { Text("Add Meal View settings ") } } .dynamicTypeSize(...DynamicTypeSize.xxLarge) diff --git a/FreeAPS/Sources/Services/Calendar/CalendarManager.swift b/FreeAPS/Sources/Services/Calendar/CalendarManager.swift index bfe0afea46..52ff1240a5 100644 --- a/FreeAPS/Sources/Services/Calendar/CalendarManager.swift +++ b/FreeAPS/Sources/Services/Calendar/CalendarManager.swift @@ -106,16 +106,10 @@ final class BaseCalendarManager: CalendarManager, Injectable { // Latest Loop data (from CoreData) var freshLoop: Double = 20 - var lastLoop = [LastLoop]() - if displeyCOBandIOB || displayEmojis { - coredataContext.performAndWait { - let requestLastLoop = LastLoop.fetchRequest() as NSFetchRequest - let sortLoops = NSSortDescriptor(key: "timestamp", ascending: false) - requestLastLoop.sortDescriptors = [sortLoops] - requestLastLoop.fetchLimit = 1 - try? lastLoop = coredataContext.fetch(requestLastLoop) - } - freshLoop = -1 * (lastLoop.first?.timestamp ?? .distantPast).timeIntervalSinceNow.minutes + var lastLoop: LastLoop? + if displeyCOBandIOB || displayEmojis, let recentLoop = CoreDataStorage().fetchLastLoop() { + lastLoop = recentLoop + freshLoop = -1 * (recentLoop.timestamp ?? .distantPast).timeIntervalSinceNow.minutes } var glucoseIcon = "🟢" @@ -137,8 +131,8 @@ final class BaseCalendarManager: CalendarManager, Injectable { .string(from: Double(settingsManager.settings.units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)! } ?? "--" - let iobText = iobFormatter.string(from: (lastLoop.first?.iob ?? 0) as NSNumber) ?? "" - let cobText = cobFormatter.string(from: (lastLoop.first?.cob ?? 0) as NSNumber) ?? "" + let iobText = lastLoop != nil ? (iobFormatter.string(from: (lastLoop?.iob ?? 0) as NSNumber) ?? "") : "" + let cobText = lastLoop != nil ? (cobFormatter.string(from: (lastLoop?.cob ?? 0) as NSNumber) ?? "") : "" var glucoseDisplayText = displayEmojis ? glucoseIcon + " " : "" glucoseDisplayText += glucoseText + " " + directionText + " " + deltaText diff --git a/FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift b/FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift index 5d9444d7b9..f1cd242460 100644 --- a/FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift +++ b/FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift @@ -7,6 +7,11 @@ struct LiveActivityAttributes: ActivityAttributes { let direction: String? let change: String let date: Date + let iob: String + let cob: String + let loopDate: Date + let eventual: String + let mmol: Bool } let startDate: Date diff --git a/FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift b/FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift index 3c4b4c5a1a..7ab223735a 100644 --- a/FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift +++ b/FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift @@ -18,23 +18,47 @@ extension LiveActivityAttributes.ContentState { formatter.roundingMode = .halfUp return formatter - .string(from: mmol ? value.asMmolL as NSNumber : NSNumber(value: value))! + .string(from: mmol ? value.asMmolL as NSNumber : NSNumber(value: value)) ?? "" } - init?(new bg: BloodGlucose, prev: BloodGlucose?, mmol: Bool) { - guard let glucose = bg.glucose else { - return nil - } - - let formattedBG = Self.formatGlucose(glucose, mmol: mmol, forceSign: false) + static func formatter(_ string: NSNumber) -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 1 + return formatter.string(from: string) ?? "" + } - let trendString = bg.direction?.symbol + static func carbFormatter(_ string: NSNumber) -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + return formatter.string(from: string) ?? "" + } - let change = prev?.glucose.map({ - Self.formatGlucose(glucose - $0, mmol: mmol, forceSign: true) - }) ?? "" + init?(new bg: Readings?, prev: Readings?, mmol: Bool, suggestion: Suggestion, loopDate: Date) { + guard let glucose = bg?.glucose else { + return nil + } - self.init(bg: formattedBG, direction: trendString, change: change, date: bg.dateString) + let formattedBG = Self.formatGlucose(Int(glucose), mmol: mmol, forceSign: false) + let trendString = bg?.direction + let change = Self.formatGlucose(Int((bg?.glucose ?? 0) - (prev?.glucose ?? 0)), mmol: mmol, forceSign: true) + let cobString = Self.carbFormatter((suggestion.cob ?? 0) as NSNumber) + let iobString = Self.formatter((suggestion.iob ?? 0) as NSNumber) + let eventual = Self.formatGlucose(suggestion.eventualBG ?? 100, mmol: mmol, forceSign: false) + let mmol = mmol + + self.init( + bg: formattedBG, + direction: trendString, + change: change, + date: bg?.date ?? Date.now, + iob: iobString, + cob: cobString, + loopDate: loopDate, + eventual: eventual, + mmol: mmol + ) } } @@ -60,7 +84,7 @@ extension LiveActivityAttributes.ContentState { @available(iOS 16.2, *) final class LiveActivityBridge: Injectable, ObservableObject { @Injected() private var settingsManager: SettingsManager! - @Injected() private var glucoseStorage: GlucoseStorage! + @Injected() private var storage: FileStorage! @Injected() private var broadcaster: Broadcaster! private let activityAuthorizationInfo = ActivityAuthorizationInfo() @@ -71,13 +95,16 @@ extension LiveActivityAttributes.ContentState { } private var currentActivity: ActiveActivity? - private var latestGlucose: BloodGlucose? + private var latestGlucose: Readings? + private var loopDate: Date? + private var suggestion: Suggestion? init(resolver: Resolver) { systemEnabled = activityAuthorizationInfo.areActivitiesEnabled injectServices(resolver) - broadcaster.register(GlucoseObserver.self, observer: self) + broadcaster.register(SuggestionObserver.self, observer: self) + broadcaster.register(EnactedSuggestionObserver.self, observer: self) Foundation.NotificationCenter.default.addObserver( forName: UIApplication.didEnterBackgroundNotification, @@ -110,15 +137,16 @@ extension LiveActivityAttributes.ContentState { } } - /// creates and tries to present a new activity update from the current GlucoseStorage values if live activities are enabled in settings + /// creates and tries to present a new activity update from the current Suggestion values if live activities are enabled in settings /// Ends existing live activities if live activities are not enabled in settings private func forceActivityUpdate() { // just before app resigns active, show a new activity // only do this if there is no current activity or the current activity is older than 1h if settings.useLiveActivity { - if currentActivity?.needsRecreation() ?? true + if currentActivity?.needsRecreation() ?? true, + let suggestion = storage.retrieveFile(OpenAPS.Enact.suggested, as: Suggestion.self) { - glucoseDidUpdate(glucoseStorage.recent()) + suggestionDidUpdate(suggestion) } } else { Task { @@ -144,7 +172,7 @@ extension LiveActivityAttributes.ContentState { } else { let content = ActivityContent( state: state, - staleDate: min(state.date, Date.now).addingTimeInterval(TimeInterval(6 * 60)) + staleDate: min(state.date, Date.now).addingTimeInterval(TimeInterval(8 * 60)) ) await currentActivity.activity.update(content) @@ -152,10 +180,18 @@ extension LiveActivityAttributes.ContentState { } else { do { // always push a non-stale content as the first update - // pushing a stale content as the frst content results in the activity not being shown at all + // pushing a stale content as the first content results in the activity not being shown at all // we want it shown though even if it is iniially stale, as we expect new BG readings to become available soon, which should then be displayed let nonStale = ActivityContent( - state: LiveActivityAttributes.ContentState(bg: "--", direction: nil, change: "--", date: Date.now), + state: LiveActivityAttributes.ContentState( + bg: "--", + direction: nil, + change: "--", + date: Date.now, + iob: "--", + cob: "--", + loopDate: Date.now, eventual: "--", mmol: false + ), staleDate: Date.now.addingTimeInterval(60) ) @@ -190,8 +226,8 @@ extension LiveActivityAttributes.ContentState { } @available(iOS 16.2, *) -extension LiveActivityBridge: GlucoseObserver { - func glucoseDidUpdate(_ glucose: [BloodGlucose]) { +extension LiveActivityBridge: SuggestionObserver, EnactedSuggestionObserver { + func enactedSuggestionDidUpdate(_ suggestion: Suggestion) { guard settings.useLiveActivity else { if currentActivity != nil { Task { @@ -200,21 +236,50 @@ extension LiveActivityBridge: GlucoseObserver { } return } - - // backfill latest glucose if contained in this update - if glucose.count > 1 { - latestGlucose = glucose[glucose.count - 2] + defer { self.suggestion = suggestion } + + let cd = CoreDataStorage() + let glucose = cd.fetchGlucose(interval: DateFilter().twoHours) + let prev = glucose.count > 1 ? glucose[1] : glucose.first + + guard let content = LiveActivityAttributes.ContentState( + new: glucose.first, + prev: prev, + mmol: settings.units == .mmolL, + suggestion: suggestion, + loopDate: (suggestion.recieved ?? false) ? (suggestion.timestamp ?? .distantPast) : + (cd.fetchLastLoop()?.timestamp ?? .distantPast) + ) else { + return } - defer { - self.latestGlucose = glucose.last + + Task { + await self.pushUpdate(content) } + } - guard let bg = glucose.last, let content = LiveActivityAttributes.ContentState( - new: bg, - prev: latestGlucose, - mmol: settings.units == .mmolL + func suggestionDidUpdate(_ suggestion: Suggestion) { + guard settings.useLiveActivity else { + if currentActivity != nil { + Task { + await self.endActivity() + } + } + return + } + defer { self.suggestion = suggestion } + + let cd = CoreDataStorage() + let glucose = cd.fetchGlucose(interval: DateFilter().twoHours) + let prev = glucose.count > 1 ? glucose[1] : glucose.first + + guard let content = LiveActivityAttributes.ContentState( + new: glucose.first, + prev: prev, + mmol: settings.units == .mmolL, + suggestion: suggestion, + loopDate: settings.closedLoop ? (cd.fetchLastLoop()?.timestamp ?? .distantPast) : suggestion.timestamp ?? .distantPast ) else { - // no bg or value, can't update the live activity return } diff --git a/FreeAPS/Sources/Shortcuts/Overrides/OverrideShortcuts.swift b/FreeAPS/Sources/Shortcuts/Overrides/OverrideShortcuts.swift index 3ac741696f..08be5e0864 100644 --- a/FreeAPS/Sources/Shortcuts/Overrides/OverrideShortcuts.swift +++ b/FreeAPS/Sources/Shortcuts/Overrides/OverrideShortcuts.swift @@ -219,6 +219,10 @@ enum OverrideIntentError: Error { // Update in Nightscout nightscoutManager.editOverride(preset, duration, activeOveride.date ?? Date.now) } + } else if activeOveride.isPreset { + if let duration = overrideStorage.cancelProfile() { + nightscoutManager.editOverride("📉", duration, activeOveride.date ?? Date.now) + } } else { let nsString = activeOveride.percentage.formatted() != "100" ? activeOveride.percentage .formatted() + " %" : "Custom" diff --git a/FreeAPS/Sources/Views/TagCloudView.swift b/FreeAPS/Sources/Views/TagCloudView.swift index f10d7edfe6..847cfbbdef 100644 --- a/FreeAPS/Sources/Views/TagCloudView.swift +++ b/FreeAPS/Sources/Views/TagCloudView.swift @@ -58,7 +58,7 @@ struct TagCloudView: View { case textTag where textTag.contains("SMB Delivery Ratio:"): return .uam case textTag where textTag.contains("Bolus"), - textTag where textTag.contains("TDD:"): + textTag where textTag.contains("Insulin 24h:"): return .purple case textTag where textTag.contains("tdd_factor"), textTag where textTag.contains("Sigmoid function"), diff --git a/FreeAPS/Sources/Views/ViewModifiers.swift b/FreeAPS/Sources/Views/ViewModifiers.swift index 5e2d71ae00..25862ffe0f 100644 --- a/FreeAPS/Sources/Views/ViewModifiers.swift +++ b/FreeAPS/Sources/Views/ViewModifiers.swift @@ -49,24 +49,20 @@ struct CompactSectionSpacing: ViewModifier { } } -struct ScrollTargetLayoutModifier: ViewModifier { - func body(content: Content) -> some View { - if #available(iOS 17, *) { - return content - .scrollTargetLayout() - } else { - return content } - } -} - -struct ScrollPositionModifier: ViewModifier { - @Binding var id: Int? - func body(content: Content) -> some View { - if #available(iOS 17, *) { - return content - .scrollPosition(id: $id) +struct InfoPanelBackground: View { + let colorScheme: ColorScheme + var body: some View { + if #available(iOS 17.0, *) { + Rectangle() + .stroke(.gray, lineWidth: 2) + .fill(colorScheme == .light ? .white : .black) + .frame(height: 24) } else { - return content } + Rectangle() + .strokeBorder(.gray, lineWidth: 2) + .background(Rectangle().fill(colorScheme == .light ? .white : .black)) + .frame(height: 24) + } } } @@ -167,7 +163,7 @@ struct LoopEllipse: View { .stroke(stroke, lineWidth: colorScheme == .light ? 2 : 1) .background( RoundedRectangle(cornerRadius: 15) - .fill(Color.white).opacity(colorScheme == .light ? 0.2 : 0.08) + .fill(colorScheme == .light ? .white : .black) ) } } @@ -205,6 +201,22 @@ struct ClockOffset: View { } } +struct TooOldValue: View { + var body: some View { + ZStack { + Image(systemName: "cicle.fill") + .resizable() + .frame(maxHeight: 20) + .symbolRenderingMode(.palette) + .foregroundStyle(Color(.warning).opacity(0.5)) + .offset(x: 5, y: -13) + .overlay { + Text("Old").font(.caption) + } + } + } +} + struct ChartBackground: ViewModifier { @Environment(\.colorScheme) var colorScheme @@ -345,14 +357,6 @@ extension View { modifier(CompactSectionSpacing()) } - func scrollTargetLayoutiOS17() -> some View { - modifier(ScrollTargetLayoutModifier()) - } - - func scrollPositioniOS17(id: Binding) -> some View { - modifier(ScrollPositionModifier(id: id)) - } - func asAny() -> AnyView { .init(self) } } diff --git a/LiveActivity/LiveActivity.swift b/LiveActivity/LiveActivity.swift index 85c7fb14e9..45939d191f 100644 --- a/LiveActivity/LiveActivity.swift +++ b/LiveActivity/LiveActivity.swift @@ -10,19 +10,27 @@ private enum Size { struct LiveActivity: Widget { private let dateFormatter: DateFormatter = { - var f = DateFormatter() - f.dateStyle = .none - f.timeStyle = .short - return f + var formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter }() + private let minuteFormatter: NumberFormatter = { + var formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + return formatter + }() + + @Environment(\.dynamicTypeSize) private var fontSize + @ViewBuilder private func changeLabel(context: ActivityViewContext) -> some View { if !context.state.change.isEmpty { - if context.isStale { - Text(context.state.change).foregroundStyle(.primary.opacity(0.5)) - .strikethrough(pattern: .solid, color: .red.opacity(0.6)) - } else { + if !context.isStale { Text(context.state.change) + } else { + Text("old").foregroundStyle(.secondary) } } else { Text("--") @@ -30,16 +38,8 @@ struct LiveActivity: Widget { } private func updatedLabel(context: ActivityViewContext) -> Text { - let text = Text("Updated: \(dateFormatter.string(from: context.state.date))") - if context.isStale { - if #available(iOSApplicationExtension 17.0, *) { - return text.bold().foregroundStyle(.red) - } else { - return text.bold().foregroundColor(.red) - } - } else { - return text - } + let text = Text("\(dateFormatter.string(from: context.state.loopDate))") + return text } private func bgAndTrend(context: ActivityViewContext, size: Size) -> (some View, Int) { @@ -77,7 +77,7 @@ struct LiveActivity: Widget { let stack = HStack(spacing: spacing) { Text(bgText) - .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6)) + if let direction = directionText { let text = Text(direction) switch size { @@ -92,28 +92,86 @@ struct LiveActivity: Widget { text.scaleEffect(x: 0.8, y: 0.8, anchor: .leading).padding(.trailing, -3) case .expanded: - text.scaleEffect(x: 0.7, y: 0.7, anchor: .leading).padding(.trailing, -5) + text.scaleEffect(x: 0.7, y: 0.7, anchor: .center).padding(.trailing, -5) } } } - .foregroundStyle(context.isStale ? Color.primary.opacity(0.5) : Color.primary) + .foregroundStyle(context.isStale ? .secondary : Color.primary) return (stack, characters) } + private func iob(context: ActivityViewContext, size _: Size) -> some View { + HStack(spacing: 0) { + Text(context.state.iob) + Text(" U") + } + .foregroundStyle(.insulin) + } + + private func cob(context: ActivityViewContext, size _: Size) -> some View { + HStack(spacing: 0) { + Text(context.state.cob) + Text(" g") + } + .foregroundStyle(.loopYellow) + } + + private func loop(context: ActivityViewContext, size: CGFloat) -> some View { + let timeAgo = abs(context.state.loopDate.timeIntervalSinceNow) / 60 + let color: Color = timeAgo > 8 ? .loopYellow : timeAgo > 12 ? .loopRed : .loopGreen + return LoopActivity(stroke: color, compact: size == 12).frame(width: size) + } + + private var emptyText: some View { + Text(" ").font(.caption).offset(x: 0, y: -5) + } + var body: some WidgetConfiguration { ActivityConfiguration(for: LiveActivityAttributes.self) { context in // Lock screen/banner UI goes here - HStack(spacing: 3) { - bgAndTrend(context: context, size: .expanded).0.font(.title) - Spacer() - VStack(alignment: .trailing, spacing: 5) { - changeLabel(context: context).font(.title3) + VStack(spacing: 2) { + ZStack { updatedLabel(context: context).font(.caption).foregroundStyle(.primary.opacity(0.7)) + .frame(maxWidth: .infinity, alignment: .trailing) + } + HStack { + VStack { + loop(context: context, size: 22) + emptyText + }.offset(x: 0, y: 2) + Spacer() + VStack { + bgAndTrend(context: context, size: .expanded).0.font(.title) + changeLabel(context: context).font(.caption).foregroundStyle(.primary.opacity(0.7)).offset(x: -12, y: -5) + } + Spacer() + VStack { + iob(context: context, size: .expanded).font(.title) + emptyText + } + Spacer() + VStack { + cob(context: context, size: .expanded).font(.title) + emptyText + } } + HStack { + Spacer() + Text(NSLocalizedString("Eventual Glucose", comment: "")) + Spacer() + Text(context.state.eventual) + Text(context.state.mmol ? NSLocalizedString( + "mmol/L", + comment: "The short unit display string for millimoles of glucose per liter" + ) : NSLocalizedString( + "mg/dL", + comment: "The short unit display string for milligrams of glucose per decilter" + )).foregroundStyle(.secondary) + }.padding(.top, 10) } .privacySensitive() - .padding(.all, 15) + .padding(.vertical, 10).padding(.horizontal, 15) // Semantic BackgroundStyle and Color values work here. They adapt to the given interface style (light mode, dark mode) // Semantic UIColors do NOT (as of iOS 17.1.1). Like UIColor.systemBackgroundColor (it does not adapt to changes of the interface style) // The colorScheme environment varaible that is usually used to detect dark mode does NOT work here (it reports false values) @@ -127,12 +185,25 @@ struct LiveActivity: Widget { DynamicIslandExpandedRegion(.leading) { bgAndTrend(context: context, size: .expanded).0.font(.title2).padding(.leading, 5) } + + DynamicIslandExpandedRegion(.center) { + HStack { + iob(context: context, size: .expanded).font(.title2).padding(.leading, 5) + cob(context: context, size: .expanded).font(.title2).padding(.horizontal, 10) + } + } + DynamicIslandExpandedRegion(.trailing) { - changeLabel(context: context).font(.title2).padding(.trailing, 5) + loop(context: context, size: 23) } DynamicIslandExpandedRegion(.bottom) { Group { - updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary) + ZStack { + changeLabel(context: context).font(.title2).padding(.trailing, 5) + .frame(maxWidth: .infinity, alignment: .leading) + updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary) + .frame(maxWidth: .infinity, alignment: .trailing) + } } .frame( maxHeight: .infinity, @@ -140,7 +211,10 @@ struct LiveActivity: Widget { ) } } compactLeading: { - bgAndTrend(context: context, size: .compact).0.padding(.leading, 4) + HStack { + loop(context: context, size: 12) + bgAndTrend(context: context, size: .compact).0.padding(.leading, 4) + } } compactTrailing: { changeLabel(context: context).padding(.trailing, 4) } minimal: { @@ -157,7 +231,7 @@ struct LiveActivity: Widget { } } .widgetURL(URL(string: "freeaps-x://")) - .keylineTint(Color.purple) + // .keylineTint(Color.purple) .contentMargins(.horizontal, 0, for: .minimal) .contentMargins(.trailing, 0, for: .compactLeading) .contentMargins(.leading, 0, for: .compactTrailing) @@ -176,24 +250,64 @@ private extension LiveActivityAttributes.ContentState { // Use mmol/l notation with decimal point as well for the same reason, it uses up to 4 characters, while mg/dl uses up to 3 static var testWide: LiveActivityAttributes.ContentState { - LiveActivityAttributes.ContentState(bg: "00.0", direction: "→", change: "+0.0", date: Date()) + LiveActivityAttributes.ContentState( + bg: "00.0", + direction: "→", + change: "+0.0", + date: Date(), + iob: "1.2", + cob: "20", + loopDate: Date.now, eventual: "100", mmol: false + ) } static var testVeryWide: LiveActivityAttributes.ContentState { - LiveActivityAttributes.ContentState(bg: "00.0", direction: "↑↑", change: "+0.0", date: Date()) + LiveActivityAttributes.ContentState( + bg: "00.0", + direction: "↑↑", + change: "+0.0", + date: Date(), + iob: "1.2", + cob: "20", + loopDate: Date.now, eventual: "100", mmol: false + ) } static var testSuperWide: LiveActivityAttributes.ContentState { - LiveActivityAttributes.ContentState(bg: "00.0", direction: "↑↑↑", change: "+0.0", date: Date()) + LiveActivityAttributes.ContentState( + bg: "00.0", + direction: "↑↑↑", + change: "+0.0", + date: Date(), + iob: "1.2", + cob: "20", + loopDate: Date.now, eventual: "100", mmol: false + ) } // 2 characters for BG, 1 character for change is the minimum that will be shown static var testNarrow: LiveActivityAttributes.ContentState { - LiveActivityAttributes.ContentState(bg: "00", direction: "↑", change: "+0", date: Date()) + LiveActivityAttributes.ContentState( + bg: "00", + direction: "↑", + change: "+0", + date: Date(), + iob: "1.2", + cob: "20", + loopDate: Date.now, eventual: "100", mmol: false + ) } static var testMedium: LiveActivityAttributes.ContentState { - LiveActivityAttributes.ContentState(bg: "000", direction: "↗︎", change: "+00", date: Date()) + LiveActivityAttributes.ContentState( + bg: "000", + direction: "↗︎", + change: "+00", + date: Date(), + iob: "1.2", + cob: "20", + loopDate: Date.now, eventual: "100", mmol: false + ) } } @@ -207,3 +321,13 @@ private extension LiveActivityAttributes.ContentState { LiveActivityAttributes.ContentState.testMedium LiveActivityAttributes.ContentState.testNarrow } + +struct LoopActivity: View { + @Environment(\.colorScheme) var colorScheme + let stroke: Color + let compact: Bool + var body: some View { + Circle() + .stroke(stroke, lineWidth: compact ? 1.5 : 3) + } +} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index aa3541aaca..5e6b47734d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -22,7 +22,7 @@ FASTLANE_KEY = ENV["FASTLANE_KEY"] DEVICE_NAME = ENV["DEVICE_NAME"] DEVICE_ID = ENV["DEVICE_ID"] ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" -APP_IDENTIFIER = "ru.artpancreas.#{TEAMID}.FreeAPS" +APP_IDENTIFIER = "com.jon.#{TEAMID}.aps" platform :ios do desc "Build iAPS" diff --git a/package.json b/package.json new file mode 100644 index 0000000000..587d4a457d --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "build": "webpack --config ./scripts/webpack.config.js" + }, + "devDependencies": { + "webpack-cli": "^5.1.4" + } +} diff --git a/scripts/webpack.config.js b/scripts/webpack.config.js index 7f070b5635..69968af4c7 100644 --- a/scripts/webpack.config.js +++ b/scripts/webpack.config.js @@ -1,31 +1,43 @@ const path = require('path'); const TerserPlugin = require("terser-webpack-plugin"); +const libPath = process.env['OREF0_DIST_PATH'] || './lib' + module.exports = { mode: 'production', entry: { - iob: './lib/iob/index.js', - meal: './lib/meal/index.js', - "determineBasal": './lib/determine-basal/determine-basal.js', - "glucoseGetLast": './lib/glucose-get-last.js', - "basalSetTemp": './lib/basal-set-temp.js', - autosens: './lib/determine-basal/autosens.js', - profile: './lib/profile/index.js', - "autotunePrep": './lib/autotune-prep/index.js', - "autotuneCore": './lib/autotune/index.js' + iob: path.resolve(libPath, 'iob/index.js'), + meal: path.resolve(libPath, 'meal/index.js'), + determineBasal: path.resolve(libPath, 'determine-basal/determine-basal.js'), + glucoseGetLast: path.resolve(libPath, 'glucose-get-last.js'), + basalSetTemp: path.resolve(libPath, 'basal-set-temp.js'), + autosens: path.resolve(libPath, 'determine-basal/autosens.js'), + profile: path.resolve(libPath, 'profile/index.js'), + autotunePrep: path.resolve(libPath, 'autotune-prep/index.js'), + autotuneCore: path.resolve(libPath, 'autotune/index.js') }, output: { - path: path.resolve(__dirname, 'dist'), + path: path.resolve(__dirname, '..', 'FreeAPS', 'Resources', 'javascript', 'bundle'), filename: (pathData) => { return pathData.chunk.name.replace(/[A-Z]/g, function(match) { return '-' + match.toLowerCase(); }) + '.js'; }, - libraryTarget: 'var', - library: 'freeaps_[name]' + library: { + type: 'var', + name: 'freeaps_[name]' + } }, optimization: { minimize: true, - minimizer: [new TerserPlugin()], + minimizer: [new TerserPlugin({ + extractComments: false, + parallel: true, + terserOptions: { + format: { + comments: false, + }, + }, + })], }, }; From 5546510163927fe44141b3fc8dc091dc6d75a45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:32:07 +0200 Subject: [PATCH 06/12] Default Bundle ID --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5e6b47734d..45166a5db7 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -22,7 +22,7 @@ FASTLANE_KEY = ENV["FASTLANE_KEY"] DEVICE_NAME = ENV["DEVICE_NAME"] DEVICE_ID = ENV["DEVICE_ID"] ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" -APP_IDENTIFIER = "com.jon.#{TEAMID}.aps" +APP_IDENTIFIER = "ru.artpancreas.$(DEVELOPMENT_TEAM).FreeAPS" platform :ios do desc "Build iAPS" From 6c77916305c42b1395e1df043621d46caaf643b6 Mon Sep 17 00:00:00 2001 From: "Jon B.M" Date: Tue, 3 Sep 2024 16:38:16 +0200 Subject: [PATCH 07/12] Version 5.0.1 --- Config.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config.xcconfig b/Config.xcconfig index 4bc8c1e9dc..0670dadebf 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -1,5 +1,5 @@ APP_DISPLAY_NAME = iAPS -APP_VERSION = 5.0.0 +APP_VERSION = 5.0.1 APP_BUILD_NUMBER = 1 COPYRIGHT_NOTICE = DEVELOPER_TEAM = ##TEAM_ID## From 30b79b7e8302772f26ed293f8f776ade5dcab415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Tue, 3 Sep 2024 17:12:44 +0200 Subject: [PATCH 08/12] Fix Fastfile --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 45166a5db7..aa3541aaca 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -22,7 +22,7 @@ FASTLANE_KEY = ENV["FASTLANE_KEY"] DEVICE_NAME = ENV["DEVICE_NAME"] DEVICE_ID = ENV["DEVICE_ID"] ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" -APP_IDENTIFIER = "ru.artpancreas.$(DEVELOPMENT_TEAM).FreeAPS" +APP_IDENTIFIER = "ru.artpancreas.#{TEAMID}.FreeAPS" platform :ios do desc "Build iAPS" From 54cd5afe57e86300812ec45ff23d8796deb70784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20M=C3=A5rtensson?= Date: Wed, 4 Sep 2024 09:44:04 +0200 Subject: [PATCH 09/12] Change loopYellow darkMode colour to a slightly more orange colour --- .../Assets.xcassets/Colors/LoopYellow.colorset/Contents.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FreeAPS/Resources/Assets.xcassets/Colors/LoopYellow.colorset/Contents.json b/FreeAPS/Resources/Assets.xcassets/Colors/LoopYellow.colorset/Contents.json index 92162bd7c4..03b3069cd8 100644 --- a/FreeAPS/Resources/Assets.xcassets/Colors/LoopYellow.colorset/Contents.json +++ b/FreeAPS/Resources/Assets.xcassets/Colors/LoopYellow.colorset/Contents.json @@ -5,8 +5,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.271", - "green" : "0.757", + "blue" : "0.000", + "green" : "0.613", "red" : "1.000" } }, From 2fd19966b9cb61d5257a29e2ff6a0c3e412cadf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20M=C3=A5rtensson?= Date: Wed, 4 Sep 2024 13:14:35 +0200 Subject: [PATCH 10/12] Dynamic Island Expanded View updates. --- LiveActivity/LiveActivity.swift | 36 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/LiveActivity/LiveActivity.swift b/LiveActivity/LiveActivity.swift index 45939d191f..7604ed5420 100644 --- a/LiveActivity/LiveActivity.swift +++ b/LiveActivity/LiveActivity.swift @@ -183,33 +183,31 @@ struct LiveActivity: Widget { // Expanded UI goes here. Compose the expanded UI through // various regions, like leading/trailing/center/bottom DynamicIslandExpandedRegion(.leading) { - bgAndTrend(context: context, size: .expanded).0.font(.title2).padding(.leading, 5) + HStack { + loop(context: context, size: 23) + }.padding(10) } DynamicIslandExpandedRegion(.center) { - HStack { - iob(context: context, size: .expanded).font(.title2).padding(.leading, 5) - cob(context: context, size: .expanded).font(.title2).padding(.horizontal, 10) + VStack(spacing: 0) { + HStack { + iob(context: context, size: .expanded).font(.title2).padding(.leading, 10) + Spacer() + cob(context: context, size: .expanded).font(.title2).padding(10) + } + HStack { + bgAndTrend(context: context, size: .expanded).0.font(.title2).padding(.leading, 10) + Spacer() + changeLabel(context: context).font(.title2).padding(10) + } } } DynamicIslandExpandedRegion(.trailing) { - loop(context: context, size: 23) - } - DynamicIslandExpandedRegion(.bottom) { - Group { - ZStack { - changeLabel(context: context).font(.title2).padding(.trailing, 5) - .frame(maxWidth: .infinity, alignment: .leading) - updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary) - .frame(maxWidth: .infinity, alignment: .trailing) - } - } - .frame( - maxHeight: .infinity, - alignment: .bottom - ) + updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary) + .padding(.trailing, 10) } + DynamicIslandExpandedRegion(.bottom) {} } compactLeading: { HStack { loop(context: context, size: 12) From f55ded16206e6125798170dbe59bd04cb5dd74e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20M=C3=A5rtensson?= Date: Wed, 4 Sep 2024 17:13:42 +0200 Subject: [PATCH 11/12] Header View alignment adjustments. Added a bit of offset to mg/dl glucose values. --- .../Modules/Home/View/Header/CurrentGlucoseView.swift | 6 +++--- FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift b/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift index 0ca6f0a066..880c5d40fa 100644 --- a/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift @@ -94,7 +94,7 @@ struct CurrentGlucoseView: View { ) .font(.caption) .foregroundStyle(.secondary) - .offset(x: 2, y: fontSize >= .extraLarge ? -3 : 0) + .offset(x: 1, y: fontSize >= .extraLarge ? -3 : 0) } } } @@ -153,13 +153,13 @@ struct CurrentGlucoseView: View { Text(decimal[1]).font(.system(size: 28)).baselineOffset(-10) } .tracking(-1) - .offset(x: 0, y: 14) + .offset(x: -2, y: 14) .foregroundColor(alwaysUseColors ? colorOfGlucose : alarm == nil ? .primary : .loopRed) } else { Text(string) .font(.glucoseFontMdDl.width(.condensed)) // .tracking(-2) .foregroundColor(alwaysUseColors ? colorOfGlucose : alarm == nil ? .primary : .loopRed) - .offset(x: 0, y: 16) + .offset(x: string.count > 2 ? -2 : -1, y: 16) } } } diff --git a/FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift b/FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift index 6ed1ceaa09..6141813ba9 100644 --- a/FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift @@ -59,7 +59,7 @@ struct LoopView: View { } }.dynamicTypeSize(...DynamicTypeSize.xLarge) } - } + }.offset(y: 5) } private var minutesAgo: Int { From c41c712ae657acce3110a76b62d0de1327c5928b Mon Sep 17 00:00:00 2001 From: "Jon B.M" Date: Thu, 5 Sep 2024 13:52:30 +0200 Subject: [PATCH 12/12] Bump version nr --- Config.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config.xcconfig b/Config.xcconfig index 0670dadebf..3e6deba9b2 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -1,5 +1,5 @@ APP_DISPLAY_NAME = iAPS -APP_VERSION = 5.0.1 +APP_VERSION = 5.0.2 APP_BUILD_NUMBER = 1 COPYRIGHT_NOTICE = DEVELOPER_TEAM = ##TEAM_ID##