Explorar o código

Merge branch 'dev' into Crowdin

Jon B.M %!s(int64=2) %!d(string=hai) anos
pai
achega
89fa86de23
Modificáronse 23 ficheiros con 541 adicións e 19 borrados
  1. 4 0
      Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents
  2. 5 1
      FreeAPS.xcodeproj/project.pbxproj
  3. 4 0
      FreeAPS/Resources/Info.plist
  4. 1 1
      FreeAPS/Resources/javascript/bundle/determine-basal.js
  5. 12 0
      FreeAPS/Resources/javascript/prepare/profile.js
  6. 0 1
      FreeAPS/Sources/APS/OpenAPS/Constants.swift
  7. 36 0
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  8. 36 0
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  9. 24 0
      FreeAPS/Sources/Models/FetchedProfile.swift
  10. 1 1
      FreeAPS/Sources/Models/NightscoutStatus.swift
  11. 24 0
      FreeAPS/Sources/Models/RawFetchedProfile.swift
  12. 32 0
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigStateModel.swift
  13. 17 0
      FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift
  14. 13 0
      FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift
  15. 4 3
      FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift
  16. 1 0
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  17. 218 0
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift
  18. 60 3
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift
  19. 39 5
      FreeAPS/Sources/Services/Calendar/CalendarManager.swift
  20. 4 0
      FreeAPS/Sources/Services/Network/NightscoutAPI.swift
  21. 3 1
      FreeAPS/Sources/Services/Network/NightscoutManager.swift
  22. 2 2
      FreeAPS/Sources/Shortcuts/State/ListStateView.swift
  23. 1 1
      FreeAPS/Sources/Views/TagCloudView.swift

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

@@ -31,6 +31,10 @@
         <attribute name="hba1c_7" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="hba1c_7" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="hba1c_30" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="hba1c_30" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
     </entity>
     </entity>
+    <entity name="ImportError" representedClassName="ImportError" syncable="YES" codeGenerationType="class">
+        <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="error" optional="YES" attributeType="String"/>
+    </entity>
     <entity name="InsulinDistribution" representedClassName="InsulinDistribution" syncable="YES" codeGenerationType="class">
     <entity name="InsulinDistribution" representedClassName="InsulinDistribution" syncable="YES" codeGenerationType="class">
         <attribute name="bolus" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="bolus" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>

+ 5 - 1
FreeAPS.xcodeproj/project.pbxproj

@@ -302,6 +302,7 @@
 		BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */; };
 		BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */; };
 		C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; };
 		C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; };
 		CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
 		CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
+		CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; };
 		CD78BB94E43B249D60CC1A1B /* NotificationsConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22963BD06A9C83959D4914E4 /* NotificationsConfigRootView.swift */; };
 		CD78BB94E43B249D60CC1A1B /* NotificationsConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22963BD06A9C83959D4914E4 /* NotificationsConfigRootView.swift */; };
 		CE2FAD38297D69E1001A872C /* ShareClient.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE398D1A297D69A900DF218F /* ShareClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		CE2FAD38297D69E1001A872C /* ShareClient.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE398D1A297D69A900DF218F /* ShareClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2FAD39297D93F0001A872C /* BloodGlucoseExtensions.swift */; };
 		CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2FAD39297D93F0001A872C /* BloodGlucoseExtensions.swift */; };
@@ -816,6 +817,7 @@
 		C19984D62EFC0035A9E9644D /* BolusProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusProvider.swift; sourceTree = "<group>"; };
 		C19984D62EFC0035A9E9644D /* BolusProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusProvider.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
 		C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusDataFlow.swift; sourceTree = "<group>"; };
 		C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusDataFlow.swift; sourceTree = "<group>"; };
+		CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = "<group>"; };
 		CE2FAD39297D93F0001A872C /* BloodGlucoseExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloodGlucoseExtensions.swift; sourceTree = "<group>"; };
 		CE2FAD39297D93F0001A872C /* BloodGlucoseExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloodGlucoseExtensions.swift; sourceTree = "<group>"; };
 		CE398D012977349800DF218F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
 		CE398D012977349800DF218F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
 		CE398D15297C9D1D00DF218F /* dexcomSourceG7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dexcomSourceG7.swift; sourceTree = "<group>"; };
 		CE398D15297C9D1D00DF218F /* dexcomSourceG7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dexcomSourceG7.swift; sourceTree = "<group>"; };
@@ -1603,6 +1605,7 @@
 				19D4E4EA29FC6A9F00351451 /* TIRforChart.swift */,
 				19D4E4EA29FC6A9F00351451 /* TIRforChart.swift */,
 				19A910352A24D6D700C8951B /* DateFilter.swift */,
 				19A910352A24D6D700C8951B /* DateFilter.swift */,
 				193F6CDC2A512C8F001240FD /* Loops.swift */,
 				193F6CDC2A512C8F001240FD /* Loops.swift */,
+				CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */,
 			);
 			);
 			path = Models;
 			path = Models;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -2470,7 +2473,7 @@
 		B99D8B8E295F34DB00420AB8 /* Run Script */ = {
 		B99D8B8E295F34DB00420AB8 /* Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
 			isa = PBXShellScriptBuildPhase;
 			alwaysOutOfDate = 1;
 			alwaysOutOfDate = 1;
-			buildActionMask = 2147483647;
+			buildActionMask = 12;
 			files = (
 			files = (
 			);
 			);
 			inputFileListPaths = (
 			inputFileListPaths = (
@@ -2689,6 +2692,7 @@
 				63E890B4D951EAA91C071D5C /* BasalProfileEditorStateModel.swift in Sources */,
 				63E890B4D951EAA91C071D5C /* BasalProfileEditorStateModel.swift in Sources */,
 				CE398D16297C9D1D00DF218F /* dexcomSourceG7.swift in Sources */,
 				CE398D16297C9D1D00DF218F /* dexcomSourceG7.swift in Sources */,
 				38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */,
 				38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */,
+				CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */,
 				385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */,
 				385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */,
 				F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */,
 				F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */,
 				38887CCE25F5725200944304 /* IOBEntry.swift in Sources */,
 				38887CCE25F5725200944304 /* IOBEntry.swift in Sources */,

+ 4 - 0
FreeAPS/Resources/Info.plist

@@ -110,6 +110,10 @@
 		<string>UIInterfaceOrientationPortrait</string>
 		<string>UIInterfaceOrientationPortrait</string>
 		<string>UIInterfaceOrientationPortraitUpsideDown</string>
 		<string>UIInterfaceOrientationPortraitUpsideDown</string>
 	</array>
 	</array>
+	<key>NSCalendarsFullAccessUsageDescription</key>
+	<string>To create events with BG reading values, so that they can be viewed on Apple Watch and CarPlay</string>
+	<key>LSApplicationCategoryType</key>
+	<string></string>
 	<key>UISupportedInterfaceOrientations~ipad</key>
 	<key>UISupportedInterfaceOrientations~ipad</key>
 	<array>
 	<array>
 		<string>UIInterfaceOrientationPortrait</string>
 		<string>UIInterfaceOrientationPortrait</string>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
FreeAPS/Resources/javascript/bundle/determine-basal.js


+ 12 - 0
FreeAPS/Resources/javascript/prepare/profile.js

@@ -63,6 +63,18 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data
     var preferences = { };
     var preferences = { };
     if (preferences_input) {
     if (preferences_input) {
         preferences = preferences_input;
         preferences = preferences_input;
+        if (preferences.curve === "rapid-acting") {
+            if (preferences.useCustomPeakTime) {
+                preferences.insulinPeakTime =
+                Math.max(50, Math.min(preferences.insulinPeakTime, 120));
+            } else { preferences.insulinPeakTime = 75; }
+        } 
+        else if (preferences.curve === "ultra-rapid") {
+            if (preferences.useCustomPeakTime) {
+                preferences.insulinPeakTime =
+                Math.max(35, Math.min(preferences.insulinPeakTime, 100));
+            } else { preferences.insulinPeakTime = 55; }
+        }
     }
     }
 
 
     var inputs = { };
     var inputs = { };

+ 0 - 1
FreeAPS/Sources/APS/OpenAPS/Constants.swift

@@ -54,7 +54,6 @@ extension OpenAPS {
         static let iob = "monitor/iob.json"
         static let iob = "monitor/iob.json"
         static let cgmState = "monitor/cgm-state.json"
         static let cgmState = "monitor/cgm-state.json"
         static let podAge = "monitor/pod-age.json"
         static let podAge = "monitor/pod-age.json"
-        // static let tdd = "monitor/tdd.json"
         static let oref2_variables = "monitor/oref2_variables.json"
         static let oref2_variables = "monitor/oref2_variables.json"
         static let alertHistory = "monitor/alerthistory.json"
         static let alertHistory = "monitor/alerthistory.json"
         static let statistics = "monitor/statistics.json"
         static let statistics = "monitor/statistics.json"

+ 36 - 0
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings

@@ -314,6 +314,42 @@ Enact a temp Basal or a temp target */
 /* Allow remote control from NS */
 /* Allow remote control from NS */
 "Remote control" = "Remote control";
 "Remote control" = "Remote control";
 
 
+/* Imported Profiles Alert */
+"\nNow please verify all of your new settings thoroughly:\n\n* Basal Settings\n * Carb Ratios\n * Glucose Targets\n * Insulin Sensitivities\n\n in iAPS Settings > Configuration.\n\nBad or invalid profile settings could have disatrous effects." = "\nNow please verify all of your new settings thoroughly:\n\n* Basal Settings\n * Carb Ratios\n * Glucose Targets\n * Insulin Sensitivities\n\n in iAPS Settings > Configuration.\n\nBad or invalid profile settings could have disatrous effects.";
+
+/* Profile Import Alert */
+"This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?" = "This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?";
+
+/* Import Error */
+"\nInvalid Nightcsout Basal Settings. \n\nImport aborted. Please check your Nightscout Profile Basal Settings!" = "\nInvalid Nightcsout Basal Settings. \n\nImport aborted. Please check your Nightscout Profile Basal Settings!";
+
+/* Import Error */
+"\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" =  "\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";
+
+/* Import Error Headline */
+"Import Error" = "Import Error";
+
+/* */
+"Yes, Import" = "Yes, Import";
+
+/* */
+"Import settings from Nightscout" = "Import settings from Nightscout";
+
+/* */
+"Import settings?" = "Import settings?";
+
+/* */
+"Import from Nightscout" = "Import from Nightscout";
+
+/* */
+"Settings imported" = "Settings imported";
+
+/* Import Error */
+"\nMismatching glucose units in Nightscout and Pump Settings. Import settings aborted." = "\nMismatching glucose units in Nightscout and Pump Settings. Import settings aborted.";
+
+/* Import Error */
+"Can't find the default Nightscout Profile." = "Can't find the default Nightscout Profile.";
+
 /* Add Medtronic pump */
 /* Add Medtronic pump */
 "Add Medtronic" = "Add Medtronic";
 "Add Medtronic" = "Add Medtronic";
 
 

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 36 - 0
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings


+ 24 - 0
FreeAPS/Sources/Models/FetchedProfile.swift

@@ -0,0 +1,24 @@
+import Foundation
+
+struct FetchedNightscoutProfileStore: JSON {
+    let _id: String
+    let defaultProfile: String
+    let startDate: String
+    let mills: Decimal
+    let enteredBy: String
+    let store: [String: ScheduledNightscoutProfile]
+    let created_at: String
+}
+
+struct FetchedNightscoutProfile: JSON {
+    let dia: Decimal
+    let carbs_hr: Int
+    let delay: Decimal
+    let timezone: String
+    let target_low: [NightscoutTimevalue]
+    let target_high: [NightscoutTimevalue]
+    let sens: [NightscoutTimevalue]
+    let basal: [NightscoutTimevalue]
+    let carbratio: [NightscoutTimevalue]
+    let units: String
+}

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

@@ -29,7 +29,7 @@ struct Uploader: JSON {
 struct NightscoutTimevalue: JSON {
 struct NightscoutTimevalue: JSON {
     let time: String
     let time: String
     let value: Decimal
     let value: Decimal
-    let timeAsSeconds: Int
+    let timeAsSeconds: Int?
 }
 }
 
 
 struct ScheduledNightscoutProfile: JSON {
 struct ScheduledNightscoutProfile: JSON {

+ 24 - 0
FreeAPS/Sources/Models/RawFetchedProfile.swift

@@ -0,0 +1,24 @@
+import Foundation
+
+struct FetchedNightscoutProfileStore: JSON {
+    let _id: String
+    let defaultProfile: String
+    let startDate: String
+    let mills: Decimal
+    let enteredBy: String
+    let store: [String: ScheduledNightscoutProfile]
+    let created_at: String
+}
+
+struct FetchedNightscoutProfile: JSON {
+    let dia: Decimal
+    let carbs_hr: Int
+    let delay: Decimal
+    let timezone: String
+    let target_low: [NightscoutTimevalue]
+    let target_high: [NightscoutTimevalue]
+    let sens: [NightscoutTimevalue]
+    let basal: [NightscoutTimevalue]
+    let carbratio: [NightscoutTimevalue]
+    let units: String
+}

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

@@ -1,9 +1,11 @@
 import Combine
 import Combine
+import LoopKit
 import SwiftUI
 import SwiftUI
 
 
 extension AutotuneConfig {
 extension AutotuneConfig {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var apsManager: APSManager!
         @Injected() var apsManager: APSManager!
+        @Injected() private var storage: FileStorage!
         @Published var useAutotune = false
         @Published var useAutotune = false
         @Published var onlyAutotuneBasals = false
         @Published var onlyAutotuneBasals = false
         @Published var autotune: Autotune?
         @Published var autotune: Autotune?
@@ -59,5 +61,35 @@ extension AutotuneConfig {
                 .cancellable()
                 .cancellable()
                 .store(in: &lifetime)
                 .store(in: &lifetime)
         }
         }
+
+        func replace() {
+            if let autotunedBasals = autotune {
+                let basals = autotunedBasals.basalProfile
+                    .map { basal -> BasalProfileEntry in
+                        BasalProfileEntry(
+                            start: String(basal.start.prefix(5)),
+                            minutes: basal.minutes,
+                            rate: basal.rate
+                        )
+                    }
+                guard let pump = apsManager.pumpManager else {
+                    storage.save(basals, as: OpenAPS.Settings.basalProfile)
+                    debug(.service, "Basals have been replaced with Autotuned Basals by user.")
+                    return
+                }
+                let syncValues = basals.map {
+                    RepeatingScheduleValue(startTime: TimeInterval($0.minutes * 60), value: Double($0.rate))
+                }
+                pump.syncBasalRateSchedule(items: syncValues) { result in
+                    switch result {
+                    case .success:
+                        self.storage.save(basals, as: OpenAPS.Settings.basalProfile)
+                        debug(.service, "Basals saved to pump!")
+                    case .failure:
+                        debug(.service, "Basals couldn't be save to pump")
+                    }
+                }
+            }
+        }
     }
     }
 }
 }

+ 17 - 0
FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift

@@ -5,6 +5,7 @@ extension AutotuneConfig {
     struct RootView: BaseView {
     struct RootView: BaseView {
         let resolver: Resolver
         let resolver: Resolver
         @StateObject var state = StateModel()
         @StateObject var state = StateModel()
+        @State var replaceAlert = false
 
 
         private var isfFormatter: NumberFormatter {
         private var isfFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -94,11 +95,27 @@ extension AutotuneConfig {
                         label: { Text("Delete autotune data") }
                         label: { Text("Delete autotune data") }
                             .foregroundColor(.red)
                             .foregroundColor(.red)
                     }
                     }
+
+                    Section {
+                        Button {
+                            replaceAlert = true
+                        }
+                        label: { Text("Save as your Normal Basal Rates") }
+                    } header: {
+                        Text("Replace Normal Basal")
+                    }
                 }
                 }
             }
             }
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Autotune")
             .navigationTitle("Autotune")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .alert(Text("Are you sure?"), isPresented: $replaceAlert) {
+                Button("Yes", action: {
+                    state.replace()
+                    replaceAlert.toggle()
+                })
+                Button("No", action: { replaceAlert.toggle() })
+            }
         }
         }
     }
     }
 }
 }

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

@@ -63,6 +63,19 @@ extension CGM {
                             }
                             }
                             Toggle("Display Emojis as Labels", isOn: $state.displayCalendarEmojis)
                             Toggle("Display Emojis as Labels", isOn: $state.displayCalendarEmojis)
                             Toggle("Display IOB and COB", isOn: $state.displayCalendarIOBandCOB)
                             Toggle("Display IOB and COB", isOn: $state.displayCalendarIOBandCOB)
+                        } else if state.createCalendarEvents {
+                            if #available(iOS 17.0, *) {
+                                Text(
+                                    "If you are not seeing calendars to choose here, please go to Settings -> iAPS -> Calendars and change permissions to \"Full Access\""
+                                ).font(.footnote)
+
+                                Button("Open Settings") {
+                                    // Get the settings URL and open it
+                                    if let url = URL(string: UIApplication.openSettingsURLString) {
+                                        UIApplication.shared.open(url)
+                                    }
+                                }
+                            }
                         }
                         }
                     }
                     }
 
 

+ 4 - 3
FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift

@@ -2,6 +2,7 @@ import SwiftUI
 
 
 struct CurrentGlucoseView: View {
 struct CurrentGlucoseView: View {
     @Binding var recentGlucose: BloodGlucose?
     @Binding var recentGlucose: BloodGlucose?
+    @Binding var timerDate: Date
     @Binding var delta: Int?
     @Binding var delta: Int?
     @Binding var units: GlucoseUnits
     @Binding var units: GlucoseUnits
     @Binding var alarm: GlucoseAlarm?
     @Binding var alarm: GlucoseAlarm?
@@ -59,10 +60,10 @@ struct CurrentGlucoseView: View {
                 image
                 image
             }
             }
             HStack {
             HStack {
-                let minutes = (recentGlucose?.dateString.timeIntervalSinceNow ?? 0) / 60
-                let text = timaAgoFormatter.string(for: Double(minutes)) ?? ""
+                let minutesAgo = -1 * (recentGlucose?.dateString.timeIntervalSinceNow ?? 0) / 60
+                let text = timaAgoFormatter.string(for: Double(minutesAgo)) ?? ""
                 Text(
                 Text(
-                    text == "0" ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
+                    minutesAgo <= 1 ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
                         text + " " +
                         text + " " +
                             NSLocalizedString("min", comment: "Short form for minutes") + " "
                             NSLocalizedString("min", comment: "Short form for minutes") + " "
                     )
                     )

+ 1 - 0
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -122,6 +122,7 @@ extension Home {
         var glucoseView: some View {
         var glucoseView: some View {
             CurrentGlucoseView(
             CurrentGlucoseView(
                 recentGlucose: $state.recentGlucose,
                 recentGlucose: $state.recentGlucose,
+                timerDate: $state.timerDate,
                 delta: $state.glucoseDelta,
                 delta: $state.glucoseDelta,
                 units: $state.units,
                 units: $state.units,
                 alarm: $state.alarm,
                 alarm: $state.alarm,

+ 218 - 0
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -1,6 +1,8 @@
 import CGMBLEKit
 import CGMBLEKit
 import Combine
 import Combine
+import CoreData
 import G7SensorKit
 import G7SensorKit
+import LoopKit
 import SwiftDate
 import SwiftDate
 import SwiftUI
 import SwiftUI
 
 
@@ -11,6 +13,10 @@ extension NightscoutConfig {
         @Injected() private var glucoseStorage: GlucoseStorage!
         @Injected() private var glucoseStorage: GlucoseStorage!
         @Injected() private var healthKitManager: HealthKitManager!
         @Injected() private var healthKitManager: HealthKitManager!
         @Injected() private var cgmManager: FetchGlucoseManager!
         @Injected() private var cgmManager: FetchGlucoseManager!
+        @Injected() private var storage: FileStorage!
+        @Injected() var apsManager: APSManager!
+
+        let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
 
 
         @Published var url = ""
         @Published var url = ""
         @Published var secret = ""
         @Published var secret = ""
@@ -22,10 +28,18 @@ extension NightscoutConfig {
         @Published var uploadGlucose = true // Upload Glucose
         @Published var uploadGlucose = true // Upload Glucose
         @Published var useLocalSource = false
         @Published var useLocalSource = false
         @Published var localPort: Decimal = 0
         @Published var localPort: Decimal = 0
+        @Published var units: GlucoseUnits = .mmolL
+        @Published var dia: Decimal = 6
+        @Published var maxBasal: Decimal = 2
+        @Published var maxBolus: Decimal = 10
 
 
         override func subscribe() {
         override func subscribe() {
             url = keychain.getValue(String.self, forKey: Config.urlKey) ?? ""
             url = keychain.getValue(String.self, forKey: Config.urlKey) ?? ""
             secret = keychain.getValue(String.self, forKey: Config.secretKey) ?? ""
             secret = keychain.getValue(String.self, forKey: Config.secretKey) ?? ""
+            units = settingsManager.settings.units
+            dia = settingsManager.pumpSettings.insulinActionCurve
+            maxBasal = settingsManager.pumpSettings.maxBasal
+            maxBolus = settingsManager.pumpSettings.maxBolus
 
 
             subscribeSetting(\.isUploadEnabled, on: $isUploadEnabled) { isUploadEnabled = $0 }
             subscribeSetting(\.isUploadEnabled, on: $isUploadEnabled) { isUploadEnabled = $0 }
             subscribeSetting(\.useLocalGlucoseSource, on: $useLocalSource) { useLocalSource = $0 }
             subscribeSetting(\.useLocalGlucoseSource, on: $useLocalSource) { useLocalSource = $0 }
@@ -68,6 +82,210 @@ extension NightscoutConfig {
                 .store(in: &lifetime)
                 .store(in: &lifetime)
         }
         }
 
 
+        private var nightscoutAPI: NightscoutAPI? {
+            guard let urlString = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey),
+                  let url = URL(string: urlString),
+                  let secret = keychain.getValue(String.self, forKey: NightscoutConfig.Config.secretKey)
+            else {
+                return nil
+            }
+            return NightscoutAPI(url: url, secret: secret)
+        }
+
+        func importSettings() {
+            guard let nightscout = nightscoutAPI else {
+                saveError("Can't access nightscoutAPI")
+                return
+            }
+            let group = DispatchGroup()
+            group.enter()
+            var error = ""
+            let path = "/api/v1/profile.json"
+            let timeout: TimeInterval = 60
+
+            var components = URLComponents()
+            components.scheme = nightscout.url.scheme
+            components.host = nightscout.url.host
+            components.port = nightscout.url.port
+            components.path = path
+            components.queryItems = [
+                URLQueryItem(name: "count", value: "1")
+            ]
+            var url = URLRequest(url: components.url!)
+            url.allowsConstrainedNetworkAccess = false
+            url.timeoutInterval = timeout
+
+            if let secret = nightscout.secret {
+                url.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
+            }
+            let task = URLSession.shared.dataTask(with: url) { data, response, error_ in
+                if let error_ = error_ {
+                    print("Error occured: " + error_.localizedDescription)
+                    // handle error
+                    self.saveError("Error occured: " + error_.localizedDescription)
+                    error = error_.localizedDescription
+                    return
+                }
+                guard let httpResponse = response as? HTTPURLResponse,
+                      (200 ... 299).contains(httpResponse.statusCode)
+                else {
+                    print("Error occured! " + error_.debugDescription)
+                    // handle error
+                    self.saveError(error_.debugDescription)
+                    return
+                }
+                let jsonDecoder = JSONCoding.decoder
+
+                if let mimeType = httpResponse.mimeType, mimeType == "application/json",
+                   let data = data
+                {
+                    do {
+                        let fetchedProfileStore = try jsonDecoder.decode([FetchedNightscoutProfileStore].self, from: data)
+                        guard let fetchedProfile: ScheduledNightscoutProfile = fetchedProfileStore.first?.store["default"]
+                        else {
+                            error = "\nCan't find the default Nightscout Profile."
+                            group.leave()
+                            return
+                        }
+
+                        guard fetchedProfile.units.contains(self.units.rawValue.prefix(4)) else {
+                            debug(
+                                .nightscout,
+                                "Mismatching glucose units in Nightscout and Pump Settings. Import settings aborted."
+                            )
+                            error = "\nMismatching glucose units in Nightscout and Pump Settings. Import settings aborted."
+                            group.leave()
+                            return
+                        }
+
+                        let carbratios = fetchedProfile.carbratio
+                            .map { carbratio -> CarbRatioEntry in
+                                CarbRatioEntry(
+                                    start: carbratio.time,
+                                    offset: (carbratio.timeAsSeconds ?? self.offset(carbratio.time)) / 60,
+                                    ratio: carbratio.value
+                                ) }
+                        let carbratiosProfile = CarbRatios(units: CarbUnit.grams, schedule: carbratios)
+
+                        var areBasalsOK = true
+                        let basals = fetchedProfile.basal
+                            .map { basal -> BasalProfileEntry in
+                                if basal.value <= 0 || basal.value >= self.maxBasal {
+                                    error =
+                                        "\nInvalid Nightcsout Basal Settings. \n\nImport aborted. Please check your Nightscout Profile Basal Settings!"
+                                    areBasalsOK = false
+                                }
+                                return BasalProfileEntry(
+                                    start: basal.time,
+                                    minutes: (basal.timeAsSeconds ?? self.offset(basal.time)) / 60,
+                                    rate: basal.value
+                                ) }
+                        guard areBasalsOK else {
+                            group.leave()
+                            return
+                        }
+                        let sensitivities = fetchedProfile.sens.map { sensitivity -> InsulinSensitivityEntry in
+                            InsulinSensitivityEntry(
+                                sensitivity: self.units == .mmolL ? sensitivity.value : sensitivity.value.asMgdL,
+                                offset: (sensitivity.timeAsSeconds ?? self.offset(sensitivity.time)) / 60,
+                                start: sensitivity.time
+                            ) }
+                        let sensitivitiesProfile = InsulinSensitivities(
+                            units: self.units,
+                            userPrefferedUnits: self.units,
+                            sensitivities: sensitivities
+                        )
+
+                        let targets = fetchedProfile.target_low
+                            .map { target -> BGTargetEntry in
+                                BGTargetEntry(
+                                    low: self.units == .mmolL ? target.value : target.value.asMgdL,
+                                    high: self.units == .mmolL ? target.value : target.value.asMgdL,
+                                    start: target.time,
+                                    offset: (target.timeAsSeconds ?? self.offset(target.time)) / 60
+                                ) }
+                        let targetsProfile = BGTargets(
+                            units: self.units,
+                            userPrefferedUnits: self.units,
+                            targets: targets
+                        )
+                        // IS THERE A PUMP?
+                        guard let pump = self.apsManager.pumpManager else {
+                            self.storage.save(carbratiosProfile, as: OpenAPS.Settings.carbRatios)
+                            self.storage.save(basals, as: OpenAPS.Settings.basalProfile)
+                            self.storage.save(sensitivitiesProfile, as: OpenAPS.Settings.insulinSensitivities)
+                            self.storage.save(targetsProfile, as: OpenAPS.Settings.bgTargets)
+                            debug(
+                                .service,
+                                "Settings 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"
+                            )
+                            error =
+                                "\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"
+                            group.leave()
+                            return
+                        }
+                        let syncValues = basals.map {
+                            RepeatingScheduleValue(startTime: TimeInterval($0.minutes * 60), value: Double($0.rate))
+                        }
+                        // SSAVE TO STORAGE. SAVE TO PUMP (LoopKit)
+                        pump.syncBasalRateSchedule(items: syncValues) { result in
+                            switch result {
+                            case .success:
+                                self.storage.save(basals, as: OpenAPS.Settings.basalProfile)
+                                self.storage.save(carbratiosProfile, as: OpenAPS.Settings.carbRatios)
+                                self.storage.save(sensitivitiesProfile, as: OpenAPS.Settings.insulinSensitivities)
+                                self.storage.save(targetsProfile, as: OpenAPS.Settings.bgTargets)
+                                debug(.service, "Settings have been imported and the Basals saved to pump!")
+                                // DIA. Save if changed.
+                                let dia = fetchedProfile.dia
+                                if dia != self.dia {
+                                    let file = PumpSettings(
+                                        insulinActionCurve: dia,
+                                        maxBolus: self.maxBolus,
+                                        maxBasal: self.maxBasal
+                                    )
+                                    self.storage.save(file, as: OpenAPS.Settings.settings)
+                                    debug(.nightscout, "DIA setting updated to " + dia.description + " after a NS import.")
+                                }
+                                group.leave()
+                            case .failure:
+                                error =
+                                    "\nSettings were imported but the Basals couldn't be saved to pump (communication error). Check your basal settings and tap ´Save on Pump´ to sync the new basal settings"
+                                debug(.service, "Basals couldn't be save to pump")
+                                group.leave()
+                            }
+                        }
+                    } catch let parsingError {
+                        print(parsingError)
+                        error = parsingError.localizedDescription
+                        group.leave()
+                    }
+                }
+            }
+            task.resume()
+            group.wait(wallTimeout: .now() + 5)
+            group.notify(queue: .global(qos: .background)) {
+                self.saveError(error)
+            }
+        }
+
+        func offset(_ string: String) -> Int {
+            let hours = Int(string.prefix(2)) ?? 0
+            let minutes = Int(string.suffix(2)) ?? 0
+            return hours * 60 + minutes * 60
+        }
+
+        func saveError(_ string: String) {
+            coredataContext.performAndWait {
+                let saveToCoreData = ImportError(context: self.coredataContext)
+                saveToCoreData.date = Date()
+                saveToCoreData.error = string
+                if coredataContext.hasChanges {
+                    try? coredataContext.save()
+                }
+            }
+        }
+
         func backfillGlucose() {
         func backfillGlucose() {
             backfilling = true
             backfilling = true
             nightscoutManager.fetchGlucose(since: Date().addingTimeInterval(-1.days.timeInterval))
             nightscoutManager.fetchGlucose(since: Date().addingTimeInterval(-1.days.timeInterval))

+ 60 - 3
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -1,3 +1,4 @@
+import CoreData
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
@@ -5,6 +6,16 @@ extension NightscoutConfig {
     struct RootView: BaseView {
     struct RootView: BaseView {
         let resolver: Resolver
         let resolver: Resolver
         @StateObject var state = StateModel()
         @StateObject var state = StateModel()
+        @State var importAlert: Alert?
+        @State var isImportAlertPresented = false
+        @State var importedHasRun = false
+
+        @FetchRequest(
+            entity: ImportError.entity(),
+            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(
+                format: "date > %@", Date().addingTimeInterval(-1.minutes.timeInterval) as NSDate
+            )
+        ) var fetchedErrors: FetchedResults<ImportError>
 
 
         private var portFormater: NumberFormatter {
         private var portFormater: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -53,14 +64,57 @@ extension NightscoutConfig {
                     Text("Allow Uploads")
                     Text("Allow Uploads")
                 }
                 }
 
 
-                Section(header: Text("Local glucose source")) {
+                Section {
+                    Button("Import settings from Nightscout") {
+                        importAlert = Alert(
+                            title: Text("Import settings?"),
+                            message: Text(
+                                "\n" +
+                                    NSLocalizedString(
+                                        "This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?",
+                                        comment: "Profile Import Alert"
+                                    ) +
+                                    "\n"
+                            ),
+                            primaryButton: .destructive(
+                                Text("Yes, Import"),
+                                action: {
+                                    state.importSettings()
+                                    importedHasRun = true
+                                }
+                            ),
+                            secondaryButton: .cancel()
+                        )
+                        isImportAlertPresented.toggle()
+                    }.disabled(state.url.isEmpty || state.connecting)
+
+                } header: { Text("Import from Nightscout") }
+
+                    .alert(isPresented: $importedHasRun) {
+                        Alert(
+                            title: Text((fetchedErrors.first?.error ?? "").count < 4 ? "Settings imported" : "Import Error"),
+                            message: Text(
+                                (fetchedErrors.first?.error ?? "").count < 4 ?
+                                    NSLocalizedString(
+                                        "\nNow please verify all of your new settings thoroughly:\n\n* Basal Settings\n * Carb Ratios\n * Glucose Targets\n * Insulin Sensitivities\n\n in iAPS Settings > Configuration.\n\nBad or invalid profile settings could have disatrous effects.",
+                                        comment: "Imported Profiles Alert"
+                                    ) :
+                                    NSLocalizedString(fetchedErrors.first?.error ?? "", comment: "Import Error")
+                            ),
+                            primaryButton: .destructive(
+                                Text("OK")
+                            ),
+                            secondaryButton: .cancel()
+                        )
+                    }
+
+                Section {
                     Toggle("Use local glucose server", isOn: $state.useLocalSource)
                     Toggle("Use local glucose server", isOn: $state.useLocalSource)
                     HStack {
                     HStack {
                         Text("Port")
                         Text("Port")
                         DecimalTextField("", value: $state.localPort, formatter: portFormater)
                         DecimalTextField("", value: $state.localPort, formatter: portFormater)
                     }
                     }
-                }
-
+                } header: { Text("Local glucose source") }
                 Section {
                 Section {
                     Button("Backfill glucose") { state.backfillGlucose() }
                     Button("Backfill glucose") { state.backfillGlucose() }
                         .disabled(state.url.isEmpty || state.connecting || state.backfilling)
                         .disabled(state.url.isEmpty || state.connecting || state.backfilling)
@@ -69,6 +123,9 @@ extension NightscoutConfig {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationBarTitle("Nightscout Config")
             .navigationBarTitle("Nightscout Config")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .alert(isPresented: $isImportAlertPresented) {
+                importAlert!
+            }
         }
         }
     }
     }
 }
 }

+ 39 - 5
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -32,17 +32,51 @@ final class BaseCalendarManager: CalendarManager, Injectable {
             let status = EKEventStore.authorizationStatus(for: .event)
             let status = EKEventStore.authorizationStatus(for: .event)
             switch status {
             switch status {
             case .notDetermined:
             case .notDetermined:
-                EKEventStore().requestAccess(to: .event) { granted, error in
-                    if let error = error {
-                        warning(.service, "Calendar access not granded", error: error)
+                #if swift(>=5.9)
+                    if #available(iOS 17.0, *) {
+                        EKEventStore().requestFullAccessToEvents(completion: { (granted: Bool, error: Error?) -> Void in
+                            if let error = error {
+                                warning(.service, "Calendar access not granted", error: error)
+                            }
+                            promise(.success(granted))
+                        })
+                    } else {
+                        EKEventStore().requestAccess(to: .event) { granted, error in
+                            if let error = error {
+                                warning(.service, "Calendar access not granted", error: error)
+                            }
+                            promise(.success(granted))
+                        }
                     }
                     }
-                    promise(.success(granted))
-                }
+                #else
+                    EKEventStore().requestAccess(to: .event) { granted, error in
+                        if let error = error {
+                            warning(.service, "Calendar access not granted", error: error)
+                        }
+                        promise(.success(granted))
+                    }
+                #endif
             case .denied,
             case .denied,
                  .restricted:
                  .restricted:
                 promise(.success(false))
                 promise(.success(false))
             case .authorized:
             case .authorized:
                 promise(.success(true))
                 promise(.success(true))
+
+            #if swift(>=5.9)
+                case .fullAccess:
+                    promise(.success(true))
+                case .writeOnly:
+                    if #available(iOS 17.0, *) {
+                        EKEventStore().requestFullAccessToEvents(completion: { (granted: Bool, error: Error?) -> Void in
+                            if let error = error {
+                                print("Calendar access not upgraded")
+                                warning(.service, "Calendar access not upgraded", error: error)
+                            }
+                            promise(.success(granted))
+                        })
+                    }
+            #endif
+
             @unknown default:
             @unknown default:
                 warning(.service, "Unknown calendar access status")
                 warning(.service, "Unknown calendar access status")
                 promise(.success(false))
                 promise(.success(false))

+ 4 - 0
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -1,6 +1,8 @@
 import Combine
 import Combine
 import CommonCrypto
 import CommonCrypto
 import Foundation
 import Foundation
+import JavaScriptCore
+import Swinject
 
 
 class NightscoutAPI {
 class NightscoutAPI {
     init(url: URL, secret: String? = nil) {
     init(url: URL, secret: String? = nil) {
@@ -27,6 +29,8 @@ class NightscoutAPI {
     let secret: String?
     let secret: String?
 
 
     private let service = NetworkService()
     private let service = NetworkService()
+
+    @Injected() private var settingsManager: SettingsManager!
 }
 }
 
 
 extension NightscoutAPI {
 extension NightscoutAPI {

+ 3 - 1
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -12,9 +12,9 @@ protocol NightscoutManager: GlucoseSource {
     func deleteCarbs(at date: Date, isFPU: Bool?, fpuID: String?, syncID: String)
     func deleteCarbs(at date: Date, isFPU: Bool?, fpuID: String?, syncID: String)
     func deleteInsulin(at date: Date)
     func deleteInsulin(at date: Date)
     func uploadStatus()
     func uploadStatus()
+    func uploadGlucose()
     func uploadStatistics(dailystat: Statistics)
     func uploadStatistics(dailystat: Statistics)
     func uploadPreferences()
     func uploadPreferences()
-    func uploadGlucose()
     func uploadProfile()
     func uploadProfile()
     var cgmURL: URL? { get }
     var cgmURL: URL? { get }
 }
 }
@@ -557,6 +557,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                     switch completion {
                     switch completion {
                     case .finished:
                     case .finished:
                         self.storage.save(glucose, as: fileToSave)
                         self.storage.save(glucose, as: fileToSave)
+                        debug(.nightscout, "Glucose uploaded")
                     case let .failure(error):
                     case let .failure(error):
                         debug(.nightscout, error.localizedDescription)
                         debug(.nightscout, error.localizedDescription)
                     }
                     }
@@ -586,6 +587,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                     switch completion {
                     switch completion {
                     case .finished:
                     case .finished:
                         self.storage.save(treatments, as: fileToSave)
                         self.storage.save(treatments, as: fileToSave)
+                        debug(.nightscout, "Treatments uploaded")
                     case let .failure(error):
                     case let .failure(error):
                         debug(.nightscout, error.localizedDescription)
                         debug(.nightscout, error.localizedDescription)
                     }
                     }

+ 2 - 2
FreeAPS/Sources/Shortcuts/State/ListStateView.swift

@@ -76,10 +76,10 @@ struct ListStateView: View {
                 image
                 image
             }
             }
             HStack {
             HStack {
-                let minutes = state.date.timeIntervalSinceNow / 60
+                let minutes = -1 * state.date.timeIntervalSinceNow / 60
                 let text = timaAgoFormatter.string(for: Double(minutes)) ?? ""
                 let text = timaAgoFormatter.string(for: Double(minutes)) ?? ""
                 Text(
                 Text(
-                    text == "0" ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
+                    minutes <= 1 ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
                         text + " " +
                         text + " " +
                             NSLocalizedString("min", comment: "Short form for minutes") + " "
                             NSLocalizedString("min", comment: "Short form for minutes") + " "
                     )
                     )

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

@@ -67,7 +67,7 @@ struct TagCloudView: View {
                  textTag where textTag.contains("Autosens/Dynamic Limit:"),
                  textTag where textTag.contains("Autosens/Dynamic Limit:"),
                  textTag where textTag.contains("Dynamic ISF/CR"),
                  textTag where textTag.contains("Dynamic ISF/CR"),
                  textTag where textTag.contains("Basal ratio"),
                  textTag where textTag.contains("Basal ratio"),
-                 textTag where textTag.contains("SMB Ratio:"):
+                 textTag where textTag.contains("SMB Ratio"):
                 return .zt
                 return .zt
             default:
             default:
                 return .insulin
                 return .insulin