Sfoglia il codice sorgente

Merge remote-tracking branch 'ivalkou/dev' into experimental

Jon B.M 4 anni fa
parent
commit
7d45ce6b58
34 ha cambiato i file con 611 aggiunte e 444 eliminazioni
  1. 2 2
      Dependencies/LoopKit/LoopKit/tr.lproj/Localizable.strings
  2. 1 1
      Dependencies/LoopKit/LoopKitUI/tr.lproj/Localizable.strings
  3. 23 23
      Dependencies/rileylink_ios/OmniKitUI/tr.lproj/OmnipodPumpManager.strings
  4. 7 7
      FreeAPS/Resources/fr.lproj/InfoPlist.strings
  5. 107 71
      FreeAPS/Sources/APS/APSManager.swift
  6. 10 0
      FreeAPS/Sources/Helpers/String+Extensions.swift
  7. 3 0
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  8. 3 0
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  9. 3 0
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  10. 3 0
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  11. 3 0
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  12. 3 0
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  13. 189 168
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  14. 3 0
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  15. 3 0
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  16. 8 5
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  17. 145 142
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  18. 3 0
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  19. 3 0
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  20. 1 0
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  21. 3 0
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  22. 3 0
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  23. 16 13
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  24. 3 0
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  25. 3 0
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  26. 17 0
      FreeAPS/Sources/Models/BasalProfileEntry.swift
  27. 17 0
      FreeAPS/Sources/Models/CarbRatios.swift
  28. 1 1
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  29. 17 0
      FreeAPS/Sources/Models/InsulinSensitivities.swift
  30. 2 1
      FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift
  31. 1 1
      FreeAPS/Sources/Modules/CREditor/CREditorStateModel.swift
  32. 1 1
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift
  33. 2 4
      README.md
  34. 2 4
      README_RU.md

+ 2 - 2
Dependencies/LoopKit/LoopKit/tr.lproj/Localizable.strings

@@ -14,7 +14,7 @@
 "com.loudnate.CarbKit.deleteCarbEntryUnownedErrorDescription" = "Yetkilendirme Reddedildi";
 "com.loudnate.CarbKit.deleteCarbEntryUnownedErrorDescription" = "Yetkilendirme Reddedildi";
 
 
 /* The error recovery suggestion when attempting to delete a sample not shared by the current app */
 /* The error recovery suggestion when attempting to delete a sample not shared by the current app */
-"com.loudnate.carbKit.sharingDeniedErrorRecoverySuggestion" = "Bu örnek Sağlık uygulamasından silinebilir";
+"com.loudnate.carbKit.sharingDeniedErrorRecoverySuggestion" = "Bu örnek Health uygulamasından silinebilir";
 
 
 /* Generic pump error description */
 /* Generic pump error description */
 "Communication Failure" = "İletişim Hatası";
 "Communication Failure" = "İletişim Hatası";
@@ -68,7 +68,7 @@
 "OK" = "Tamam";
 "OK" = "Tamam";
 
 
 /* The error recovery suggestion when Health sharing was denied */
 /* The error recovery suggestion when Health sharing was denied */
-"Please re-enable sharing in Health" = "Lütfen Sağlık Uygulaması paylaşımını yeniden etkinleştirin";
+"Please re-enable sharing in Health" = "Lütfen Health paylaşımı yeniden etkinleştirin";
 
 
 /* Glucose trend up */
 /* Glucose trend up */
 "Rising" = "Yükselmekte";
 "Rising" = "Yükselmekte";

+ 1 - 1
Dependencies/LoopKit/LoopKitUI/tr.lproj/Localizable.strings

@@ -45,7 +45,7 @@
 "Basal, bolus, and correction insulin dose amounts are unaffected." = "Bazal, bolus ve düzeltme insülin doz miktarları etkilenmez.";
 "Basal, bolus, and correction insulin dose amounts are unaffected." = "Bazal, bolus ve düzeltme insülin doz miktarları etkilenmez.";
 
 
 /* The title of the cancel action in an action sheet */
 /* The title of the cancel action in an action sheet */
-"Cancel" = "İptal Et";
+"Cancel" = "Vazgeç";
 
 
 /* The text for the override cancellation button */
 /* The text for the override cancellation button */
 "Cancel Override" = "Geçersiz kılmayı İptal et";
 "Cancel Override" = "Geçersiz kılmayı İptal et";

+ 23 - 23
Dependencies/rileylink_ios/OmniKitUI/tr.lproj/OmnipodPumpManager.strings

@@ -1,68 +1,68 @@
 /* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */
 /* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */
-"1LF-te-Bdd.headerTitle" = "Remove POD";
+"1LF-te-Bdd.headerTitle" = "POD'u kaldır";
 
 
 /* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */
 /* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */
-"3HH-eJ-lRh.title" = "RileyLink Setup";
+"3HH-eJ-lRh.title" = "RileyLink Kurulumu";
 
 
 /* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */
 /* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */
-"6vo-Ov-UpE.title" = "Pod Settings";
+"6vo-Ov-UpE.title" = "Pod Ayarları";
 
 
 /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */
 /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */
-"91O-Un-vKc.title" = "Pump Setup";
+"91O-Un-vKc.title" = "Pompa Kurulumu";
 
 
 /* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */
 /* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */
-"EUt-xk-Rmp.footerTitle" = "NOTE: Do not remove the pod's needle cap at this time.";
+"EUt-xk-Rmp.footerTitle" = "NOT: Şu anda pod'un plastik iğne kapağını çıkarmayın.";
 
 
 /* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */
 /* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */
-"EUt-xk-Rmp.headerTitle" = "Prepare Pod";
+"EUt-xk-Rmp.headerTitle" = "Pod'u Hazırla";
 
 
 /* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */
 /* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */
-"Eng-IY-fQ7.text" = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you.";
+"Eng-IY-fQ7.text" = "Döngü, süresi dolmadan önce pod'u değiştirmenizi hatırlatacaktır. Bunun için değişimi size uygun olan bir zamanda yapabilirsiniz.";
 
 
 /* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */
 /* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */
-"GK7-jb-tyY.text" = "Please deactivate the pod. When deactivation is complete, remove pod from body.";
+"GK7-jb-tyY.text" = "Lütfen pod'u devre dışı bırakın. Devre dışı bırakma tamamlandığında, pod'u vücudunuzdan çıkarın.";
 
 
 /* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */
 /* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */
-"HwT-30-f0y.title" = "Insert Cannula";
+"HwT-30-f0y.title" = "Kanül Yerleştir";
 
 
 /* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */
 /* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */
-"Iuv-5M-bDH.text" = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site.";
+"Iuv-5M-bDH.text" = "İnfüzyon bölgesini hazırlayın. Pod'un plastik iğne kapağını ve yapışkan desteğini çıkarın. Herşey yolunda ise pod'u infüzyon bölgesine yapıştırın.";
 
 
 /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */
 /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */
-"aNg-mm-Uuy.title" = "Pump Setup";
+"aNg-mm-Uuy.title" = "Pompa Kurulumu";
 
 
 /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */
 /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */
-"ack-ra-XH6.title" = "Pump Setup";
+"ack-ra-XH6.title" = "Pompa Kurulumu";
 
 
 /* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */
 /* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */
-"bJ5-iH-fnF.text" = "Your Pod is ready for use.";
+"bJ5-iH-fnF.text" = "Pod'unuz kullanıma hazır.";
 
 
 /* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */
 /* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */
-"ePA-6p-q8C.text" = "Reminder";
+"ePA-6p-q8C.text" = "Hatırlatıcı";
 
 
 /* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */
 /* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */
-"jVO-Ut-MhL.title" = "Pod Pairing";
+"jVO-Ut-MhL.title" = "Pod Eşleştiriliyor";
 
 
 /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */
 /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */
-"k1Y-x4-m0a.title" = "Pump Setup";
+"k1Y-x4-m0a.title" = "Pompa Kurulumu";
 
 
 /* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */
 /* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */
-"kLL-SQ-K0a.text" = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen.";
+"kLL-SQ-K0a.text" = "Aşağıdaki ayarlarınızı gözden geçirin. Pod eşleştirme sırasında bu ayarlar programlanacaktır. Bu ayarları istediğiniz zaman Loop'un Ayarlar ekranından değiştirebilirsiniz.";
 
 
 /* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */
 /* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */
-"nDb-R5-e02.title" = "Setup Complete";
+"nDb-R5-e02.title" = "Kurulum tamamlandı";
 
 
 /* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */
 /* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */
-"rcC-ke-lUP.footerTitle" = "NOTE: If cannula sticks out, press cancel.";
+"rcC-ke-lUP.footerTitle" = "NOT: Kanül dışarı çıkarsa, iptal düğmesine basın.";
 
 
 /* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */
 /* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */
-"rcC-ke-lUP.headerTitle" = "Apply POD";
+"rcC-ke-lUP.headerTitle" = "POD'u yerleştirin";
 
 
 /* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */
 /* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */
-"vEc-Km-ewe.text" = "Label";
+"vEc-Km-ewe.text" = "Etiket";
 
 
 /* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */
 /* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */
-"vmF-Dc-3DS.text" = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep the RileyLink about 6 inches from the pod during pairing.";
+"vmF-Dc-3DS.text" = "Yeni bir pod'u insülinle doldurun. Doldurma sırasında pod'tan gelen 2 bip sesini dinleyin. Eşleştirme sırasında RileyLink'i bölmeden yaklaşık 6 inç ( ≈ 15cm) uzakta tutun.";
 
 
 /* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */
 /* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */
-"yy1-xf-HdR.title" = "Replace Pod";
+"yy1-xf-HdR.title" = "Pod'u değiştir";

+ 7 - 7
FreeAPS/Resources/fr.lproj/InfoPlist.strings

@@ -1,20 +1,20 @@
 /* Privacy - NFC Scan Usage Description */
 /* Privacy - NFC Scan Usage Description */
-"NFCReaderUsageDescription" = "NFC is used to scan Libre sensors.";
+"NFCReaderUsageDescription" = "NFC est utilisé pour scanner les capteurs Libre.";
 
 
 /* Privacy - Bluetooth Always Usage Description */
 /* Privacy - Bluetooth Always Usage Description */
-"NSBluetoothAlwaysUsageDescription" = "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices";
+"NSBluetoothAlwaysUsageDescription" = "Bluetooth est utilisé pour communiquer avec la pompe à insuline et les dispositifs de surveillance continue du glucose";
 
 
 /* Privacy - Bluetooth Peripheral Usage Description */
 /* Privacy - Bluetooth Peripheral Usage Description */
-"NSBluetoothPeripheralUsageDescription" = "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices";
+"NSBluetoothPeripheralUsageDescription" = "Bluetooth est utilisé pour communiquer avec la pompe à insuline et les dispositifs de surveillance continue du glucose";
 
 
 /* Privacy - Face ID Usage Description */
 /* Privacy - Face ID Usage Description */
-"NSFaceIDUsageDescription" = "For authorized acces to bolus";
+"NSFaceIDUsageDescription" = "Pour les accès autorisés au bolus";
 
 
 /* Privacy - Calendars Usage Description */
 /* Privacy - Calendars Usage Description */
-"NSCalendarsUsageDescription" = "Calendar is used to create a new glucose events.";
+"NSCalendarsUsageDescription" = "Le calendrier est utilisé pour créer un nouvel événement de glycémie.";
 
 
 /* Privacy - Health Update Usage Description */
 /* Privacy - Health Update Usage Description */
-"NSHealthUpdateUsageDescription" = "Health App is used to store blood glucose data";
+"NSHealthUpdateUsageDescription" = "L'application Santé est utilisée pour stocker les données de glycémie";
 
 
 /* Privacy - Health Share Usage Description */
 /* Privacy - Health Share Usage Description */
-"NSHealthShareUsageDescription" = "Health App is used to store blood glucose data";
+"NSHealthShareUsageDescription" = "L'application Santé est utilisée pour stocker les données de glycémie";

+ 107 - 71
FreeAPS/Sources/APS/APSManager.swift

@@ -108,9 +108,7 @@ final class BaseAPSManager: APSManager, Injectable {
         lastLoopDateSubject.send(lastLoopDate)
         lastLoopDateSubject.send(lastLoopDate)
 
 
         isLooping
         isLooping
-            .sink { value in
-                self.deviceDataManager.loopInProgress = value
-            }
+            .weakAssign(to: \.deviceDataManager.loopInProgress, on: self)
             .store(in: &lifetime)
             .store(in: &lifetime)
     }
     }
 
 
@@ -147,6 +145,7 @@ final class BaseAPSManager: APSManager, Injectable {
         deviceDataManager.heartbeat(date: date)
         deviceDataManager.heartbeat(date: date)
     }
     }
 
 
+    // Loop entry point
     private func loop() {
     private func loop() {
         guard !isLooping.value else {
         guard !isLooping.value else {
             warning(.apsManager, "Already looping, skip")
             warning(.apsManager, "Already looping, skip")
@@ -156,51 +155,71 @@ final class BaseAPSManager: APSManager, Injectable {
         debug(.apsManager, "Starting loop")
         debug(.apsManager, "Starting loop")
         isLooping.send(true)
         isLooping.send(true)
         determineBasal()
         determineBasal()
-            .sink { [weak self] ok in
-                guard let self = self else { return }
+            .replaceEmpty(with: false)
+            .flatMap { [weak self] success -> AnyPublisher<Void, Error> in
+                guard let self = self, success else {
+                    return Fail(error: APSError.apsError(message: "Determine basal failed")).eraseToAnyPublisher()
+                }
 
 
-                if ok {
-                    self.nightscout.uploadStatus()
-                    if self.settings.closedLoop {
-                        self.enactSuggested()
-                    } else {
-                        self.isLooping.send(false)
-                        self.lastLoopDate = Date()
-                    }
+                // Open loop completed
+                guard self.settings.closedLoop else {
+                    return Just(()).setFailureType(to: Error.self).eraseToAnyPublisher()
+                }
+
+                self.nightscout.uploadStatus()
+
+                // Closed loop - enact suggested
+                return self.enactSuggested()
+            }
+            .sink { [weak self] completion in
+                guard let self = self else { return }
+                if case let .failure(error) = completion {
+                    self.loopCompleted(error: error)
                 } else {
                 } else {
-                    self.isLooping.send(false)
+                    self.loopCompleted()
                 }
                 }
-            }.store(in: &lifetime)
+            } receiveValue: {}
+            .store(in: &lifetime)
     }
     }
 
 
-    private func verifyStatus() -> Bool {
+    // Loop exit point
+    private func loopCompleted(error: Error? = nil) {
+        isLooping.send(false)
+
+        if let error = error {
+            warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
+            processError(error)
+        } else {
+            debug(.apsManager, "Loop succeeded")
+            lastLoopDate = Date()
+            lastError.send(nil)
+        }
+
+        if settings.closedLoop {
+            reportEnacted(received: error == nil)
+        }
+    }
+
+    private func verifyStatus() -> Error? {
         guard let pump = pumpManager else {
         guard let pump = pumpManager else {
-            debug(.apsManager, "Pump is not set")
-            processError(APSError.invalidPumpState(message: "Pump not set"))
-            return false
+            return APSError.invalidPumpState(message: "Pump not set")
         }
         }
         let status = pump.status.pumpStatus
         let status = pump.status.pumpStatus
 
 
         guard !status.bolusing else {
         guard !status.bolusing else {
-            debug(.apsManager, "Pump is bolusing")
-            processError(APSError.invalidPumpState(message: "Pump is bolusing"))
-            return false
+            return APSError.invalidPumpState(message: "Pump is bolusing")
         }
         }
 
 
         guard !status.suspended else {
         guard !status.suspended else {
-            debug(.apsManager, "Pump suspended")
-            processError(APSError.invalidPumpState(message: "Pump suspended"))
-            return false
+            return APSError.invalidPumpState(message: "Pump suspended")
         }
         }
 
 
         let reservoir = storage.retrieve(OpenAPS.Monitor.reservoir, as: Decimal.self) ?? 100
         let reservoir = storage.retrieve(OpenAPS.Monitor.reservoir, as: Decimal.self) ?? 100
         guard reservoir > 0 else {
         guard reservoir > 0 else {
-            debug(.apsManager, "Reservoir is empty")
-            processError(APSError.invalidPumpState(message: "Reservoir is empty"))
-            return false
+            return APSError.invalidPumpState(message: "Reservoir is empty")
         }
         }
 
 
-        return true
+        return nil
     }
     }
 
 
     private func autosens() -> AnyPublisher<Bool, Never> {
     private func autosens() -> AnyPublisher<Bool, Never> {
@@ -301,7 +320,17 @@ final class BaseAPSManager: APSManager, Injectable {
     private var bolusReporter: DoseProgressReporter?
     private var bolusReporter: DoseProgressReporter?
 
 
     func enactBolus(amount: Double, isSMB: Bool) {
     func enactBolus(amount: Double, isSMB: Bool) {
-        guard let pump = pumpManager, verifyStatus() else { return }
+        if let error = verifyStatus() {
+            processError(error)
+            processQueue.async {
+                self.broadcaster.notify(BolusFailureObserver.self, on: self.processQueue) {
+                    $0.bolusDidFail()
+                }
+            }
+            return
+        }
+
+        guard let pump = pumpManager else { return }
 
 
         let roundedAmout = pump.roundToSupportedBolusVolume(units: amount)
         let roundedAmout = pump.roundToSupportedBolusVolume(units: amount)
 
 
@@ -348,7 +377,12 @@ final class BaseAPSManager: APSManager, Injectable {
     }
     }
 
 
     func enactTempBasal(rate: Double, duration: TimeInterval) {
     func enactTempBasal(rate: Double, duration: TimeInterval) {
-        guard let pump = pumpManager, verifyStatus() else { return }
+        if let error = verifyStatus() {
+            processError(error)
+            return
+        }
+
+        guard let pump = pumpManager else { return }
         debug(.apsManager, "Enact temp basal \(rate) - \(duration)")
         debug(.apsManager, "Enact temp basal \(rate) - \(duration)")
 
 
         let roundedAmout = pump.roundToSupportedBasalRate(unitsPerHour: rate)
         let roundedAmout = pump.roundToSupportedBasalRate(unitsPerHour: rate)
@@ -402,7 +436,8 @@ final class BaseAPSManager: APSManager, Injectable {
 
 
         switch action {
         switch action {
         case let .bolus(amount):
         case let .bolus(amount):
-            guard verifyStatus() else {
+            if let error = verifyStatus() {
+                processError(error)
                 return
                 return
             }
             }
             let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount))
             let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount))
@@ -419,7 +454,8 @@ final class BaseAPSManager: APSManager, Injectable {
         case let .pump(pumpAction):
         case let .pump(pumpAction):
             switch pumpAction {
             switch pumpAction {
             case .suspend:
             case .suspend:
-                guard verifyStatus(), !pump.status.pumpStatus.suspended else {
+                if let error = verifyStatus() {
+                    processError(error)
                     return
                     return
                 }
                 }
                 pump.suspendDelivery { error in
                 pump.suspendDelivery { error in
@@ -450,7 +486,11 @@ final class BaseAPSManager: APSManager, Injectable {
             debug(.apsManager, "Closed loop \(closedLoop) by Announcement")
             debug(.apsManager, "Closed loop \(closedLoop) by Announcement")
             announcementsStorage.storeAnnouncements([announcement], enacted: true)
             announcementsStorage.storeAnnouncements([announcement], enacted: true)
         case let .tempbasal(rate, duration):
         case let .tempbasal(rate, duration):
-            guard verifyStatus(), !settings.closedLoop else {
+            if let error = verifyStatus() {
+                processError(error)
+                return
+            }
+            guard !settings.closedLoop else {
                 return
                 return
             }
             }
             let roundedRate = pump.roundToSupportedBasalRate(unitsPerHour: Double(rate))
             let roundedRate = pump.roundToSupportedBasalRate(unitsPerHour: Double(rate))
@@ -489,30 +529,27 @@ final class BaseAPSManager: APSManager, Injectable {
         }
         }
     }
     }
 
 
-    private func enactSuggested() {
+    private func enactSuggested() -> AnyPublisher<Void, Error> {
         guard let suggested = storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self) else {
         guard let suggested = storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self) else {
-            isLooping.send(false)
-            warning(.apsManager, "Suggestion not found")
-            processError(APSError.apsError(message: "Suggestion not found"))
-            return
+            return Fail(error: APSError.apsError(message: "Suggestion not found")).eraseToAnyPublisher()
         }
         }
 
 
         guard Date().timeIntervalSince(suggested.deliverAt ?? .distantPast) < Config.eхpirationInterval else {
         guard Date().timeIntervalSince(suggested.deliverAt ?? .distantPast) < Config.eхpirationInterval else {
-            isLooping.send(false)
-            warning(.apsManager, "Suggestion expired")
-            processError(APSError.apsError(message: "Suggestion expired"))
-            return
+            return Fail(error: APSError.apsError(message: "Suggestion expired")).eraseToAnyPublisher()
         }
         }
 
 
         guard let pump = pumpManager else {
         guard let pump = pumpManager else {
-            isLooping.send(false)
-            warning(.apsManager, "Pump not set")
-            processError(APSError.invalidPumpState(message: "Pump not set"))
-            return
+            return Fail(error: APSError.apsError(message: "Pump not set")).eraseToAnyPublisher()
         }
         }
 
 
         let basalPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
         let basalPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
-            guard let rate = suggested.rate, let duration = suggested.duration, self.verifyStatus() else {
+            if let error = self.verifyStatus() {
+                return Fail(error: error).eraseToAnyPublisher()
+            }
+
+            guard let rate = suggested.rate, let duration = suggested.duration else {
+                // It is OK, no temp required
+                debug(.apsManager, "No temp required")
                 return Just(()).setFailureType(to: Error.self)
                 return Just(()).setFailureType(to: Error.self)
                     .eraseToAnyPublisher()
                     .eraseToAnyPublisher()
             }
             }
@@ -525,7 +562,12 @@ final class BaseAPSManager: APSManager, Injectable {
         }.eraseToAnyPublisher()
         }.eraseToAnyPublisher()
 
 
         let bolusPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
         let bolusPublisher: AnyPublisher<Void, Error> = Deferred { () -> AnyPublisher<Void, Error> in
-            guard let units = suggested.units, self.verifyStatus() else {
+            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)
                 return Just(()).setFailureType(to: Error.self)
                     .eraseToAnyPublisher()
                     .eraseToAnyPublisher()
             }
             }
@@ -536,26 +578,11 @@ final class BaseAPSManager: APSManager, Injectable {
             .eraseToAnyPublisher()
             .eraseToAnyPublisher()
         }.eraseToAnyPublisher()
         }.eraseToAnyPublisher()
 
 
-        basalPublisher
-            .flatMap { bolusPublisher }
-            .sink { [weak self] completion in
-                if case let .failure(error) = completion {
-                    warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
-                    self?.reportEnacted(suggestion: suggested, received: false)
-                    self?.processError(APSError.pumpError(error))
-                } else {
-                    self?.reportEnacted(suggestion: suggested, received: true)
-                }
-                self?.isLooping.send(false)
-            } receiveValue: {
-                debug(.apsManager, "Loop succeeded")
-                self.lastError.send(nil)
-                self.lastLoopDate = Date()
-            }.store(in: &lifetime)
+        return basalPublisher.flatMap { bolusPublisher }.eraseToAnyPublisher()
     }
     }
 
 
-    private func reportEnacted(suggestion: Suggestion, received: Bool) {
-        if suggestion.deliverAt != nil {
+    private func reportEnacted(received: Bool) {
+        if let suggestion = storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self), suggestion.deliverAt != nil {
             var enacted = suggestion
             var enacted = suggestion
             enacted.timestamp = Date()
             enacted.timestamp = Date()
             enacted.recieved = received
             enacted.recieved = received
@@ -602,7 +629,9 @@ private extension PumpManager {
                     promise(.failure(error))
                     promise(.failure(error))
                 }
                 }
             }
             }
-        }.eraseToAnyPublisher()
+        }
+        .mapError { APSError.pumpError($0) }
+        .eraseToAnyPublisher()
     }
     }
 
 
     func enactBolus(units: Double, automatic: Bool) -> AnyPublisher<DoseEntry, Error> {
     func enactBolus(units: Double, automatic: Bool) -> AnyPublisher<DoseEntry, Error> {
@@ -617,7 +646,9 @@ private extension PumpManager {
                     promise(.failure(error))
                     promise(.failure(error))
                 }
                 }
             }
             }
-        }.eraseToAnyPublisher()
+        }
+        .mapError { APSError.pumpError($0) }
+        .eraseToAnyPublisher()
     }
     }
 
 
     func cancelBolus() -> AnyPublisher<DoseEntry?, Error> {
     func cancelBolus() -> AnyPublisher<DoseEntry?, Error> {
@@ -633,6 +664,7 @@ private extension PumpManager {
                 }
                 }
             }
             }
         }
         }
+        .mapError { APSError.pumpError($0) }
         .eraseToAnyPublisher()
         .eraseToAnyPublisher()
     }
     }
 
 
@@ -645,7 +677,9 @@ private extension PumpManager {
                     promise(.success(()))
                     promise(.success(()))
                 }
                 }
             }
             }
-        }.eraseToAnyPublisher()
+        }
+        .mapError { APSError.pumpError($0) }
+        .eraseToAnyPublisher()
     }
     }
 
 
     func resumeDelivery() -> AnyPublisher<Void, Error> {
     func resumeDelivery() -> AnyPublisher<Void, Error> {
@@ -657,7 +691,9 @@ private extension PumpManager {
                     promise(.success(()))
                     promise(.success(()))
                 }
                 }
             }
             }
-        }.eraseToAnyPublisher()
+        }
+        .mapError { APSError.pumpError($0) }
+        .eraseToAnyPublisher()
     }
     }
 }
 }
 
 

+ 10 - 0
FreeAPS/Sources/Helpers/String+Extensions.swift

@@ -1,3 +1,5 @@
+import Foundation
+
 extension String {
 extension String {
     func capitalizingFirstLetter() -> String {
     func capitalizingFirstLetter() -> String {
         prefix(1).capitalized + dropFirst()
         prefix(1).capitalized + dropFirst()
@@ -7,3 +9,11 @@ extension String {
         self = capitalizingFirstLetter()
         self = capitalizingFirstLetter()
     }
     }
 }
 }
+
+extension LosslessStringConvertible {
+    var string: String { .init(self) }
+}
+
+extension FloatingPoint where Self: LosslessStringConvertible {
+    var decimal: Decimal? { Decimal(string: string) }
+}

+ 3 - 0
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

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

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

+ 3 - 0
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

+ 3 - 0
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS Haupteinstellungen";
 "OpenAPS main settings" = "OpenAPS Haupteinstellungen";

+ 3 - 0
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

+ 3 - 0
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

File diff suppressed because it is too large
+ 189 - 168
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings


+ 3 - 0
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

+ 3 - 0
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings

@@ -897,6 +897,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "Impostazioni principali di OpenAPS";
 "OpenAPS main settings" = "Impostazioni principali di OpenAPS";

+ 8 - 5
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings

@@ -879,19 +879,22 @@ Enact a temp Basal or a temp target */
 "Bolus failed or inaccurate. Check pump history before repeating." = "Bolus mislyktes eller var upresis. Kontroller bolus-historikken før du gjentar.";
 "Bolus failed or inaccurate. Check pump history before repeating." = "Bolus mislyktes eller var upresis. Kontroller bolus-historikken før du gjentar.";
 
 
 /* */
 /* */
-"Carbs" = "Carbs";
+"Carbs" = "Karbo";
 
 
 /* */
 /* */
-"Temp Basal" = "Temp Basal";
+"Temp Basal" = "Midlertidig basal";
 
 
 /* */
 /* */
-"Temp Target" = "Temp Target";
+"Temp Target" = "Midlertidig mål";
 
 
 /* */
 /* */
-"Resume" = "Resume";
+"Resume" = "Gjenoppta leveranse";
 
 
 /* */
 /* */
-"Suspend" = "Suspend";
+"Suspend" = "Pause leveranse";
+
+/* */
+"Animated Background" = "Animert bakgrunn";
 
 
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */

File diff suppressed because it is too large
+ 145 - 142
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings


+ 3 - 0
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings

@@ -895,6 +895,9 @@ Połączono z Nightscout!";
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

+ 3 - 0
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

+ 1 - 0
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings

@@ -896,6 +896,7 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Animated Background" = "Анимированный фон";
 "Animated Background" = "Анимированный фон";
 
 
+
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "Основные настройки OpenAPS";
 "OpenAPS main settings" = "Основные настройки OpenAPS";
 
 

+ 3 - 0
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

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

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Pausa";
 "Suspend" = "Pausa";
 
 
+/* */
+"Animated Background" = "Animerad Bakgrund";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS huvudsakliga inställningar";
 "OpenAPS main settings" = "OpenAPS huvudsakliga inställningar";

+ 16 - 13
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings

@@ -522,7 +522,7 @@ Enact a temp Basal or a temp target */
 "Pair Sensor & connect" = "Sensör eşleştir ve bağlan";
 "Pair Sensor & connect" = "Sensör eşleştir ve bağlan";
 
 
 /* */
 /* */
-"Phone NFC required!" = "Telefon NFC gerekli!";
+"Phone NFC required!" = "NFC özellikli telefon gerekli!";
 
 
 /* */
 /* */
 "Your phone or app is not enabled for NFC communications, which is needed to pair to libre2 sensors" = "Telefonunuz veya uygulamanız, libre2 sensörleriyle eşleştirmek için gerekli olan NFC iletişimleri için etkin değil";
 "Your phone or app is not enabled for NFC communications, which is needed to pair to libre2 sensors" = "Telefonunuz veya uygulamanız, libre2 sensörleriyle eşleştirmek için gerekli olan NFC iletişimleri için etkin değil";
@@ -540,7 +540,7 @@ Enact a temp Basal or a temp target */
 "Delete CGMManager and start anew. Your libreoopweb credentials will be preserved" = "CGMManager'ı silin ve yeniden başlayın. libreoopweb kimlik bilgileriniz korunacaktır";
 "Delete CGMManager and start anew. Your libreoopweb credentials will be preserved" = "CGMManager'ı silin ve yeniden başlayın. libreoopweb kimlik bilgileriniz korunacaktır";
 
 
 /* Invalid libre checksum */
 /* Invalid libre checksum */
-"Invalid libre checksum" = "Geçersiz libre sağlama toplamı";
+"Invalid libre checksum" = "Libre sağlaması geçersiz";
 
 
 /* Libre sensor was incorrectly read, CRCs were not valid */
 /* Libre sensor was incorrectly read, CRCs were not valid */
 "Libre sensor was incorrectly read, CRCs were not valid" = "Libre sensörü hatalı okundu, CRC'ler geçersizdi";
 "Libre sensor was incorrectly read, CRCs were not valid" = "Libre sensörü hatalı okundu, CRC'ler geçersizdi";
@@ -570,10 +570,10 @@ Enact a temp Basal or a temp target */
 "This might be an intermittent problem, but please check that your transmitter is tightly secured over your sensor" = "Bu aralıklı bir sorun olabilir ancak lütfen vericinizin sensörünüze sıkıca sabitlendiğini kontrol edin";
 "This might be an intermittent problem, but please check that your transmitter is tightly secured over your sensor" = "Bu aralıklı bir sorun olabilir ancak lütfen vericinizin sensörünüze sıkıca sabitlendiğini kontrol edin";
 
 
 /* New Sensor Detected */
 /* New Sensor Detected */
-"New Sensor Detected" = "Yemi Sensör Tespit Edildi";
+"New Sensor Detected" = "Yeni Sensör Algılandı";
 
 
 /* Please wait up to 30 minutes before glucose readings are available! */
 /* Please wait up to 30 minutes before glucose readings are available! */
-"Please wait up to 30 minutes before glucose readings are available!" = "Lütfen glikoz ölçümleri mevcut olana kadar 30 dakikaya kadar bekleyin!";
+"Please wait up to 30 minutes before glucose readings are available!" = "Lütfen glikoz ölçümleri mevcut olana kadar 30 dakika bekleyin!";
 
 
 /* Invalid Glucose sample detected, try again later */
 /* Invalid Glucose sample detected, try again later */
 "Invalid Glucose sample detected, try again later" = "Geçersiz Glikoz örneği tespit edildi, daha sonra tekrar deneyin";
 "Invalid Glucose sample detected, try again later" = "Geçersiz Glikoz örneği tespit edildi, daha sonra tekrar deneyin";
@@ -624,7 +624,7 @@ Enact a temp Basal or a temp target */
 "Last measurement" = "Son ölçüm";
 "Last measurement" = "Son ölçüm";
 
 
 /* */
 /* */
-"Sensor Footer checksum" = "Sensör Altbilgi sağlama toplamı";
+"Sensor Footer checksum" = "Sensör Altbilgi sağlaması";
 
 
 /* */
 /* */
 "Last Blood Sugar prediction" = "Son KŞ'i tahmini";
 "Last Blood Sugar prediction" = "Son KŞ'i tahmini";
@@ -642,7 +642,7 @@ Enact a temp Basal or a temp target */
 "Sensor Age Left" = "Kalan Sensör Yaşı";
 "Sensor Age Left" = "Kalan Sensör Yaşı";
 
 
 /* */
 /* */
-"Sensor Endtime" = "Sensör Bitim zamanı";
+"Sensor Endtime" = "Sensör Bitiş zamanı";
 
 
 /* */
 /* */
 "Sensor State" = "Sensör Durumu";
 "Sensor State" = "Sensör Durumu";
@@ -780,10 +780,10 @@ Enact a temp Basal or a temp target */
 "High Glucose Alarm active" = "Yüksek Glikoz Uyarılarımı aktif";
 "High Glucose Alarm active" = "Yüksek Glikoz Uyarılarımı aktif";
 
 
 /* */
 /* */
-"Low Glucose Alarm active" = "Düşük Glikoz Uyarılarımı aktif";
+"Low Glucose Alarm active" = "Düşük Glikoz Alarmı aktif";
 
 
 /* */
 /* */
-"No Glucose Alarm active" = "Glikoz Uyarılarımı aktif değil";
+"No Glucose Alarm active" = "Glikoz Alarmı aktif değil";
 
 
 /* */
 /* */
 "snoozing until %@" = "%@'e kadar erteleniyor";
 "snoozing until %@" = "%@'e kadar erteleniyor";
@@ -879,19 +879,22 @@ Enact a temp Basal or a temp target */
 "Bolus failed or inaccurate. Check pump history before repeating." = "Bolus başarısız veya hatalı. Tekrarlamadan önce pompa geçmişini kontrol edin.";
 "Bolus failed or inaccurate. Check pump history before repeating." = "Bolus başarısız veya hatalı. Tekrarlamadan önce pompa geçmişini kontrol edin.";
 
 
 /* */
 /* */
-"Carbs" = "Carbs";
+"Carbs" = "Karbonhidrat";
 
 
 /* */
 /* */
-"Temp Basal" = "Temp Basal";
+"Temp Basal" = "Geçici Bazal Oranı";
 
 
 /* */
 /* */
-"Temp Target" = "Temp Target";
+"Temp Target" = "Geçici Hedef";
 
 
 /* */
 /* */
-"Resume" = "Resume";
+"Resume" = "Devam et";
 
 
 /* */
 /* */
-"Suspend" = "Suspend";
+"Suspend" = "Askıya al";
+
+/* */
+"Animated Background" = "Animasyonlu Arka Plan";
 
 
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */

+ 3 - 0
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";
 "OpenAPS main settings" = "OpenAPS main settings";

+ 3 - 0
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 /* */
 "Suspend" = "Suspend";
 "Suspend" = "Suspend";
 
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 
 /* Headers for settings ----------------------- */
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS 主要设置";
 "OpenAPS main settings" = "OpenAPS 主要设置";

+ 17 - 0
FreeAPS/Sources/Models/BasalProfileEntry.swift

@@ -9,3 +9,20 @@ struct BasalProfileEntry: JSON, Equatable {
 protocol BasalProfileObserver {
 protocol BasalProfileObserver {
     func basalProfileDidChange(_ basalProfile: [BasalProfileEntry])
     func basalProfileDidChange(_ basalProfile: [BasalProfileEntry])
 }
 }
+
+extension BasalProfileEntry {
+    private enum CodingKeys: String, CodingKey {
+        case start
+        case minutes
+        case rate
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        let start = try container.decode(String.self, forKey: .start)
+        let minutes = try container.decode(Int.self, forKey: .minutes)
+        let rate = try container.decode(Double.self, forKey: .rate).decimal ?? .zero
+
+        self = BasalProfileEntry(start: start, minutes: minutes, rate: rate)
+    }
+}

+ 17 - 0
FreeAPS/Sources/Models/CarbRatios.swift

@@ -15,3 +15,20 @@ enum CarbUnit: String, JSON {
     case grams
     case grams
     case exchanges
     case exchanges
 }
 }
+
+extension CarbRatioEntry {
+    private enum CodingKeys: String, CodingKey {
+        case start
+        case offset
+        case ratio
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        let start = try container.decode(String.self, forKey: .start)
+        let offset = try container.decode(Int.self, forKey: .offset)
+        let ratio = try container.decode(Double.self, forKey: .ratio).decimal ?? .zero
+
+        self = CarbRatioEntry(start: start, offset: offset, ratio: ratio)
+    }
+}

+ 1 - 1
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -22,7 +22,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var lowGlucose: Decimal = 72
     var lowGlucose: Decimal = 72
     var highGlucose: Decimal = 270
     var highGlucose: Decimal = 270
     var carbsRequiredThreshold: Decimal = 10
     var carbsRequiredThreshold: Decimal = 10
-    var animatedBackground: Bool = true
+    var animatedBackground: Bool = false
 }
 }
 
 
 extension FreeAPSSettings: Decodable {
 extension FreeAPSSettings: Decodable {

+ 17 - 0
FreeAPS/Sources/Models/InsulinSensitivities.swift

@@ -19,3 +19,20 @@ struct InsulinSensitivityEntry: JSON {
     let offset: Int
     let offset: Int
     let start: String
     let start: String
 }
 }
+
+extension InsulinSensitivityEntry {
+    private enum CodingKeys: String, CodingKey {
+        case sensitivity
+        case offset
+        case start
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        let sensitivity = try container.decode(Double.self, forKey: .sensitivity).decimal ?? .zero
+        let start = try container.decode(String.self, forKey: .start)
+        let offset = try container.decode(Int.self, forKey: .offset)
+
+        self = InsulinSensitivityEntry(sensitivity: sensitivity, offset: offset, start: start)
+    }
+}

+ 2 - 1
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -15,7 +15,8 @@ extension BasalProfileEditor {
         }
         }
 
 
         override func subscribe() {
         override func subscribe() {
-            rateValues = provider.supportedBasalRates ?? stride(from: Decimal(0.05), to: 10.01, by: 0.05).map { $0 }
+            rateValues = provider.supportedBasalRates ?? stride(from: 5.0, to: 1001.0, by: 5.0)
+                .map { ($0.decimal ?? .zero) / 100 }
             items = provider.profile.map { value in
             items = provider.profile.map { value in
                 let timeIndex = timeValues.firstIndex(of: Double(value.minutes * 60)) ?? 0
                 let timeIndex = timeValues.firstIndex(of: Double(value.minutes * 60)) ?? 0
                 let rateIndex = rateValues.firstIndex(of: value.rate) ?? 0
                 let rateIndex = rateValues.firstIndex(of: value.rate) ?? 0

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

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

+ 1 - 1
FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -14,7 +14,7 @@ extension ISFEditor {
             case .mgdL:
             case .mgdL:
                 return stride(from: 9, to: 540.01, by: 1.0).map { Decimal($0) }
                 return stride(from: 9, to: 540.01, by: 1.0).map { Decimal($0) }
             case .mmolL:
             case .mmolL:
-                return stride(from: 0.1, to: 30.01, by: 0.1).map { Decimal($0) }
+                return stride(from: 1.0, to: 301.0, by: 1.0).map { ($0.decimal ?? .zero) / 10 }
             }
             }
         }
         }
 
 

+ 2 - 4
README.md

@@ -12,7 +12,7 @@ FreeAPS X uses original JavaScript files of oref0 and provides a user interface
 
 
 ## Smartphone requirements
 ## Smartphone requirements
 
 
-- All iPhones which support iOS 14 and up.
+- All iPhones which support iOS 15 and up.
 
 
 ## Supported pumps
 ## Supported pumps
 
 
@@ -39,13 +39,11 @@ A stable version means that it has been tested for a long time and does not cont
 
 
 Stable version numbers end in **.0**.
 Stable version numbers end in **.0**.
 
 
-The current stable version is 0.2.0. (**DOES NOT work on iOS 15! Use beta versions**)
-
 ### Beta versions
 ### Beta versions
 
 
 Beta versions are the first to introduce new functionality. They are designed to test and identify issues and bugs.
 Beta versions are the first to introduce new functionality. They are designed to test and identify issues and bugs.
 
 
-**Beta versions are less stable, use with caution!**
+**Beta versions are fairly stable, but may contain occasional bugs.**
 
 
 Beta numbers end with a number greater than **0**.
 Beta numbers end with a number greater than **0**.
 
 

+ 2 - 4
README_RU.md

@@ -12,7 +12,7 @@ FreeAPS X использует оригинальные JavaScript файлы or
 
 
 ## Требования к смартфону
 ## Требования к смартфону
 
 
-- Все iPhone с поддержкой iOS 14 и выше.
+- Все iPhone с поддержкой iOS 15 и выше.
 
 
 ## Поддерживаемые помпы
 ## Поддерживаемые помпы
 
 
@@ -39,13 +39,11 @@ FreeAPS X находится в состоянии активной разраб
 
 
 Номера стабильных версий заканчиваются на **.0**.
 Номера стабильных версий заканчиваются на **.0**.
 
 
-Текущая стабильная версия - 0.2.0 (**НЕ работает на iOS 15! Используйте бета-версии**).
-
 ### Бета-версии
 ### Бета-версии
 
 
 В бета-версиях впервые появляется новая функциональность. Они предназначены для тестирования и выявления проблем и багов.
 В бета-версиях впервые появляется новая функциональность. Они предназначены для тестирования и выявления проблем и багов.
 
 
-**Бета-версии менее стабильны, используйте с осторожностью!**
+**Бета-версии довольно стабильны, но могут содержать случайные ошибки.**
 
 
 Номера бета-версий заканчиваются на число больше **0**.
 Номера бета-версий заканчиваются на число больше **0**.