Procházet zdrojové kódy

First implementation of siri shortcuts

Pierre L před 3 roky
rodič
revize
0de3ee724b

+ 64 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -318,6 +318,16 @@
 		CE79502C29980CB500FA576E /* G7SensorKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502929980C9F00FA576E /* G7SensorKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		CE79502E29980E4D00FA576E /* ShareClientUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502D29980E4D00FA576E /* ShareClientUI.framework */; };
 		CE79502F29980E5800FA576E /* ShareClientUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502D29980E4D00FA576E /* ShareClientUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		CE7CA34E2A064973004BE681 /* AppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3432A064973004BE681 /* AppShortcuts.swift */; };
+		CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */; };
+		CE7CA3502A064973004BE681 /* CancelTempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */; };
+		CE7CA3512A064973004BE681 /* ApplyTempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */; };
+		CE7CA3522A064973004BE681 /* ListTempPresetsIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3482A064973004BE681 /* ListTempPresetsIntent.swift */; };
+		CE7CA3532A064973004BE681 /* tempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3492A064973004BE681 /* tempPresetIntent.swift */; };
+		CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */; };
+		CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34C2A064973004BE681 /* ListStateIntent.swift */; };
+		CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34D2A064973004BE681 /* StateIntentRequest.swift */; };
+		CE7CA3582A064E2F004BE681 /* ListStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3572A064E2F004BE681 /* ListStateView.swift */; };
 		CE82E02528E867BA00473A9C /* AlertStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE82E02428E867BA00473A9C /* AlertStorage.swift */; };
 		CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE82E02628E869DF00473A9C /* AlertEntry.swift */; };
 		CE94597E29E9E1EE0047C9C6 /* GarminManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE94597D29E9E1EE0047C9C6 /* GarminManager.swift */; };
@@ -812,6 +822,16 @@
 		CE79502729980C9600FA576E /* CGMBLEKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CGMBLEKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CE79502929980C9F00FA576E /* G7SensorKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = G7SensorKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CE79502D29980E4D00FA576E /* ShareClientUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ShareClientUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		CE7CA3432A064973004BE681 /* AppShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppShortcuts.swift; sourceTree = "<group>"; };
+		CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseIntentsRequest.swift; sourceTree = "<group>"; };
+		CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelTempPresetIntent.swift; sourceTree = "<group>"; };
+		CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyTempPresetIntent.swift; sourceTree = "<group>"; };
+		CE7CA3482A064973004BE681 /* ListTempPresetsIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTempPresetsIntent.swift; sourceTree = "<group>"; };
+		CE7CA3492A064973004BE681 /* tempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tempPresetIntent.swift; sourceTree = "<group>"; };
+		CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempPresetsIntentRequest.swift; sourceTree = "<group>"; };
+		CE7CA34C2A064973004BE681 /* ListStateIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListStateIntent.swift; sourceTree = "<group>"; };
+		CE7CA34D2A064973004BE681 /* StateIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateIntentRequest.swift; sourceTree = "<group>"; };
+		CE7CA3572A064E2F004BE681 /* ListStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStateView.swift; sourceTree = "<group>"; };
 		CE82E02428E867BA00473A9C /* AlertStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertStorage.swift; sourceTree = "<group>"; };
 		CE82E02628E869DF00473A9C /* AlertEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertEntry.swift; sourceTree = "<group>"; };
 		CE94597929E9DF7B0047C9C6 /* ConnectIQ.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ConnectIQ.framework; path = "Dependencies/ios-armv7_arm64/ConnectIQ.framework"; sourceTree = "<group>"; };
@@ -1162,6 +1182,7 @@
 		3811DE1325C9D39E00A708ED /* Sources */ = {
 			isa = PBXGroup;
 			children = (
+				CE7CA3422A064973004BE681 /* Shortcuts */,
 				3811DEDE25C9E2DD00A708ED /* Application */,
 				3811DF0A25CAAAA500A708ED /* APS */,
 				E00EEBFC27368630002FF094 /* Assemblies */,
@@ -2019,6 +2040,39 @@
 			path = Bolus;
 			sourceTree = "<group>";
 		};
+		CE7CA3422A064973004BE681 /* Shortcuts */ = {
+			isa = PBXGroup;
+			children = (
+				CE7CA3432A064973004BE681 /* AppShortcuts.swift */,
+				CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */,
+				CE7CA3452A064973004BE681 /* TempPresets */,
+				CE7CA34B2A064973004BE681 /* State */,
+			);
+			path = Shortcuts;
+			sourceTree = "<group>";
+		};
+		CE7CA3452A064973004BE681 /* TempPresets */ = {
+			isa = PBXGroup;
+			children = (
+				CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */,
+				CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */,
+				CE7CA3482A064973004BE681 /* ListTempPresetsIntent.swift */,
+				CE7CA3492A064973004BE681 /* tempPresetIntent.swift */,
+				CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */,
+			);
+			path = TempPresets;
+			sourceTree = "<group>";
+		};
+		CE7CA34B2A064973004BE681 /* State */ = {
+			isa = PBXGroup;
+			children = (
+				CE7CA34C2A064973004BE681 /* ListStateIntent.swift */,
+				CE7CA34D2A064973004BE681 /* StateIntentRequest.swift */,
+				CE7CA3572A064E2F004BE681 /* ListStateView.swift */,
+			);
+			path = State;
+			sourceTree = "<group>";
+		};
 		CE94597C29E9E1CD0047C9C6 /* WatchConfig */ = {
 			isa = PBXGroup;
 			children = (
@@ -2440,6 +2494,7 @@
 				38D0B3B625EBE24900CB6E88 /* Battery.swift in Sources */,
 				38C4D33725E9A1A300D30B77 /* DispatchQueue+Extensions.swift in Sources */,
 				F90692CF274B999A0037068D /* HealthKitDataFlow.swift in Sources */,
+				CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */,
 				3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */,
 				CEA4F62329BE10F70011ADF7 /* SavitzkyGolayFilter.swift in Sources */,
 				38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */,
@@ -2564,6 +2619,7 @@
 				38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */,
 				38F37828261260DC009DB701 /* Color+Extensions.swift in Sources */,
 				3811DE3F25C9D4A100A708ED /* SettingsStateModel.swift in Sources */,
+				CE7CA3582A064E2F004BE681 /* ListStateView.swift in Sources */,
 				38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */,
 				38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */,
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,
@@ -2572,6 +2628,7 @@
 				3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,
 				45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */,
 				3871F38725ED661C0013ECB5 /* Suggestion.swift in Sources */,
+				CE7CA34E2A064973004BE681 /* AppShortcuts.swift in Sources */,
 				38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */,
 				38DF179027733EAD00B3528F /* SnowScene.swift in Sources */,
 				38AAF8712600C1B0004AF583 /* MainChartView.swift in Sources */,
@@ -2582,6 +2639,7 @@
 				72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */,
 				E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */,
 				45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */,
+				CE7CA3532A064973004BE681 /* tempPresetIntent.swift in Sources */,
 				D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */,
 				38E98A3025F52FF700C0CED0 /* Config.swift in Sources */,
 				BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */,
@@ -2593,6 +2651,7 @@
 				5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */,
 				19D466AA29AA3099004D5F33 /* FPUConfigRootView.swift in Sources */,
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
+				CE7CA3522A064973004BE681 /* ListTempPresetsIntent.swift in Sources */,
 				448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */,
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,
 				2BE9A6FA20875F6F4F9CD461 /* PumpSettingsEditorProvider.swift in Sources */,
@@ -2609,6 +2668,7 @@
 				385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */,
 				8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */,
 				389442CB25F65F7100FA1F27 /* NightscoutTreatment.swift in Sources */,
+				CE7CA3512A064973004BE681 /* ApplyTempPresetIntent.swift in Sources */,
 				FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */,
 				63E890B4D951EAA91C071D5C /* BasalProfileEditorStateModel.swift in Sources */,
 				CE398D16297C9D1D00DF218F /* dexcomSourceG7.swift in Sources */,
@@ -2637,6 +2697,7 @@
 				98641AF4F92123DA668AB931 /* CREditorRootView.swift in Sources */,
 				38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */,
 				38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */,
+				CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */,
 				F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */,
 				5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */,
 				E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */,
@@ -2646,6 +2707,7 @@
 				38E8754F275556FA00975559 /* WatchManager.swift in Sources */,
 				A228DF96647338139F152B15 /* PreferencesEditorDataFlow.swift in Sources */,
 				389ECE052601144100D86C4F /* ConcurrentMap.swift in Sources */,
+				CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */,
 				E4984C5262A90469788754BB /* PreferencesEditorProvider.swift in Sources */,
 				DD399FB31EACB9343C944C4C /* PreferencesEditorStateModel.swift in Sources */,
 				19E1F7EA29D082ED005C8D20 /* IconConfigProvider.swift in Sources */,
@@ -2654,6 +2716,7 @@
 				CE48C86428CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift in Sources */,
 				38E8755427561E9800975559 /* DataFlow.swift in Sources */,
 				38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */,
+				CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */,
 				A6F097A14CAAE0CE0D11BE1B /* AddCarbsProvider.swift in Sources */,
 				33E198D3039045D98C3DC5D4 /* AddCarbsStateModel.swift in Sources */,
 				28089E07169488CF6DCC2A31 /* AddCarbsRootView.swift in Sources */,
@@ -2713,6 +2776,7 @@
 				0F7A65FBD2CD8D6477ED4539 /* NotificationsConfigProvider.swift in Sources */,
 				3171D2818C7C72CD1584BB5E /* NotificationsConfigStateModel.swift in Sources */,
 				CD78BB94E43B249D60CC1A1B /* NotificationsConfigRootView.swift in Sources */,
+				CE7CA3502A064973004BE681 /* CancelTempPresetIntent.swift in Sources */,
 				6B1F539F9FF75646D1606066 /* SnoozeDataFlow.swift in Sources */,
 				6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */,
 				8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */,

+ 21 - 0
FreeAPS/Sources/Shortcuts/AppShortcuts.swift

@@ -0,0 +1,21 @@
+import AppIntents
+import Foundation
+
+@available(iOS 16.0, *) struct AppShortcuts: AppShortcutsProvider {
+    @AppShortcutsBuilder static var appShortcuts: [AppShortcut] {
+        AppShortcut(
+            intent: ApplyTempPresetIntent(),
+            phrases: [
+                "Activate \(.applicationName) temporary target ?",
+                "\(.applicationName) apply a temporary target"
+            ]
+        )
+        AppShortcut(
+            intent: ListStateIntent(),
+            phrases: [
+                "List \(.applicationName) state",
+                "\(.applicationName) state"
+            ]
+        )
+    }
+}

+ 23 - 0
FreeAPS/Sources/Shortcuts/BaseIntentsRequest.swift

@@ -0,0 +1,23 @@
+import Foundation
+import Swinject
+
+@available(iOS 16.0, *) protocol IntentsRequestType {
+    var intentRequest: BaseIntentsRequest { get set }
+}
+
+@available(iOS 16.0, *) class BaseIntentsRequest: NSObject, Injectable {
+    @Injected() var tempTargetsStorage: TempTargetsStorage!
+    @Injected() var settingsManager: SettingsManager!
+    @Injected() var storage: TempTargetsStorage!
+    @Injected() var fileStorage: FileStorage!
+
+    let resolver: Resolver
+
+    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
+
+    override init() {
+        resolver = FreeAPSApp.resolver
+        super.init()
+        injectServices(resolver)
+    }
+}

+ 41 - 0
FreeAPS/Sources/Shortcuts/State/ListStateIntent.swift

@@ -0,0 +1,41 @@
+import AppIntents
+import Foundation
+
+@available(iOS 16.0, *) struct ListStateIntent: AppIntent {
+    // Title of the action in the Shortcuts app
+    static var title: LocalizedStringResource = "List last state available with iAPS"
+
+    var stateIntent = StateIntentRequest()
+
+    // Description of the action in the Shortcuts app
+    static var description = IntentDescription(
+        "Allow to list the last Blood Glucose, trends, IOB and COB available in iAPS"
+    )
+
+    static var parameterSummary: some ParameterSummary {
+        Summary("List all states of iAPS")
+    }
+
+    @MainActor func perform() async throws -> some ReturnsValue<StateiAPSResults> & ShowsSnippetView {
+        let glucoseValues = try? stateIntent.getLastBG()
+        let iob_cob_value = try? stateIntent.getIOB_COB()
+
+        guard let glucoseValue = glucoseValues else { throw StateIntentError.NoBG }
+        guard let iob_cob = iob_cob_value else { throw StateIntentError.NoIOBCOB }
+        let BG = StateiAPSResults(
+            glucose: glucoseValue.glucose,
+            trend: glucoseValue.trend,
+            delta: glucoseValue.delta,
+            date: glucoseValue.dateGlucose,
+            iob: iob_cob.iob,
+            cob: iob_cob.cob,
+            unit: stateIntent.settingsManager.settings.units
+        )
+        let iob_text = String(format: "%.2f", iob_cob.iob)
+        let cob_text = String(format: "%.2f", iob_cob.cob)
+        return .result(
+            value: BG,
+            view: ListStateView(state: BG)
+        )
+    }
+}

+ 128 - 0
FreeAPS/Sources/Shortcuts/State/ListStateView.swift

@@ -0,0 +1,128 @@
+import AppIntents
+import Foundation
+import SwiftUI
+
+struct ListStateView: View {
+    var state: StateiAPSResults
+
+    private var numberFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 2
+        return formatter
+    }
+
+    private var glucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        if state.unit == "mmolL" {
+            formatter.minimumFractionDigits = 1
+            formatter.maximumFractionDigits = 1
+        }
+        formatter.roundingMode = .halfUp
+        return formatter
+    }
+
+    private var deltaFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        formatter.positivePrefix = "  +"
+        formatter.negativePrefix = "  -"
+        return formatter
+    }
+
+    private var timaAgoFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        formatter.negativePrefix = ""
+        return formatter
+    }
+
+    private var dateFormatter: DateFormatter {
+        let formatter = DateFormatter()
+        formatter.timeStyle = .short
+        return formatter
+    }
+
+    var body: some View {
+        HStack(alignment: .center) {
+            Spacer()
+
+            HStack {
+                Text("IOB").font(.caption).foregroundColor(.secondary)
+                Text(
+                    (numberFormatter.string(from: (state.iob ?? 0) as NSNumber) ?? "0") +
+                        NSLocalizedString(" U", comment: "Insulin unit")
+                )
+                .font(.body).fontWeight(.bold)
+            }
+            HStack {
+                Text("COB").font(.caption).foregroundColor(.secondary)
+                Text(
+                    (numberFormatter.string(from: (state.cob ?? 0) as NSNumber) ?? "0") +
+                        NSLocalizedString(" g", comment: "gram of carbs")
+                )
+                .font(.body).fontWeight(.bold)
+            }
+            Spacer()
+            HStack {
+                Text(
+                    state.glucose
+                )
+                .font(.title).fontWeight(.bold).foregroundColor(.loopGreen)
+                image
+            }
+            HStack {
+                let minutes = state.date.timeIntervalSinceNow / 60
+                let text = timaAgoFormatter.string(for: Double(minutes)) ?? ""
+                Text(
+                    text == "0" ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
+                        text + " " +
+                            NSLocalizedString("min", comment: "Short form for minutes") + " "
+                    )
+                )
+                .font(.caption2).foregroundColor(.secondary)
+
+                Text(
+                    state.delta
+                )
+                .font(.caption2).foregroundColor(.secondary)
+            }
+            Spacer()
+        }
+        .frame(maxWidth: .infinity)
+        .padding(.top, 6)
+        .padding(.bottom, 6)
+        //       .background(Color.gray.opacity(0.2))
+    }
+
+    var image: Image {
+        let direction = state.trend
+        switch direction {
+        case "DoubleUp",
+             "SingleUp",
+             "TripleUp":
+            return Image(systemName: "arrow.up")
+        case "FortyFiveUp":
+            return Image(systemName: "arrow.up.right")
+        case "Flat":
+            return Image(systemName: "arrow.forward")
+        case "FortyFiveDown":
+            return Image(systemName: "arrow.down.forward")
+        case "DoubleDown",
+             "SingleDown",
+             "TripleDown":
+            return Image(systemName: "arrow.down")
+
+        case "NONE",
+             "NOT COMPUTABLE",
+             "RATE OUT OF RANGE":
+            return Image(systemName: "arrow.left.and.right")
+        default:
+            return Image(systemName: "arrow.left.and.right")
+        }
+    }
+}

+ 116 - 0
FreeAPS/Sources/Shortcuts/State/StateIntentRequest.swift

@@ -0,0 +1,116 @@
+import AppIntents
+import Foundation
+
+enum StateIntentError: Error {
+    case StateIntentUnknownError
+    case NoBG
+    case NoIOBCOB
+}
+
+@available(iOS 16, *) struct StateiAPSResults: AppEntity {
+    static var defaultQuery = StateBGQuery()
+
+    static var typeDisplayRepresentation: TypeDisplayRepresentation = "iAPS State Result"
+
+    var displayRepresentation: DisplayRepresentation {
+        DisplayRepresentation(title: "\(glucose)")
+    }
+
+    var id: UUID
+    @Property(title: "Glucose") var glucose: String
+
+    @Property(title: "Trend") var trend: String
+
+    @Property(title: "Delta") var delta: String
+
+    @Property(title: "Date") var date: Date
+
+    @Property(title: "IOB") var iob: Double?
+
+    @Property(title: "COB") var cob: Double?
+
+    @Property(title: "unit") var unit: String?
+
+    init(glucose: String, trend: String, delta: String, date: Date, iob: Double, cob: Double, unit: GlucoseUnits) {
+        id = UUID()
+        self.glucose = glucose
+        self.trend = trend
+        self.delta = delta
+        self.date = date
+        self.iob = iob
+        self.cob = cob
+        self.unit = unit.rawValue
+    }
+}
+
+@available(iOS 16.0, *) struct StateBGQuery: EntityQuery {
+    func entities(for _: [StateiAPSResults.ID]) async throws -> [StateiAPSResults] {
+        []
+    }
+
+    func suggestedEntities() async throws -> [StateiAPSResults] {
+        []
+    }
+}
+
+@available(iOS 16.0, *) final class StateIntentRequest: BaseIntentsRequest {
+    @Injected() private var glucoseStorage: GlucoseStorage!
+    @Injected() private var carbsStorage: CarbsStorage!
+    @Injected() private var apsManager: APSManager!
+
+    func getLastBG() throws -> (dateGlucose: Date, glucose: String, trend: String, delta: String) {
+        let glucose = glucoseStorage.recent()
+        guard let lastGlucose = glucose.last, let glucoseValue = lastGlucose.glucose else { throw StateIntentError.NoBG }
+        let delta = glucose.count >= 2 ? glucoseValue - (glucose[glucose.count - 2].glucose ?? 0) : nil
+        let units = settingsManager.settings.units
+
+        let glucoseText = glucoseFormatter
+            .string(from: Double(
+                units == .mmolL ? glucoseValue
+                    .asMmolL : Decimal(glucoseValue)
+            ) as NSNumber)!
+        let directionText = lastGlucose.direction?.rawValue ?? "none"
+        let deltaText = delta
+            .map {
+                self.deltaFormatter
+                    .string(from: Double(
+                        units == .mmolL ? $0
+                            .asMmolL : Decimal($0)
+                    ) as NSNumber)!
+            } ?? "--"
+
+        return (lastGlucose.dateString, glucoseText, directionText, deltaText)
+    }
+
+    func getIOB_COB() throws -> (iob: Double, cob: Double) {
+        let iob = suggestion?.iob ?? 0.0
+        let cob = suggestion?.cob ?? 0.0
+        let iob_double = Double(truncating: iob as NSNumber)
+        let cob_double = Double(truncating: cob as NSNumber)
+        return (iob_double, cob_double)
+    }
+
+    private var suggestion: Suggestion? {
+        fileStorage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
+    }
+
+    private var glucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        if settingsManager.settings.units == .mmolL {
+            formatter.minimumFractionDigits = 1
+            formatter.maximumFractionDigits = 1
+        }
+        formatter.roundingMode = .halfUp
+        return formatter
+    }
+
+    private var deltaFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        formatter.positivePrefix = "+"
+        return formatter
+    }
+}

+ 68 - 0
FreeAPS/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift

@@ -0,0 +1,68 @@
+import AppIntents
+import Foundation
+
+@available(iOS 16.0, *) struct ApplyTempPresetIntent: AppIntent {
+    // Title of the action in the Shortcuts app
+    static var title: LocalizedStringResource = "Apply a temporary Preset"
+
+    // Description of the action in the Shortcuts app
+    static var description = IntentDescription("Allow to apply a specific temporary preset.")
+
+    internal var intentRequest: TempPresetsIntentRequest
+
+    init() {
+        intentRequest = TempPresetsIntentRequest()
+    }
+
+    @Parameter(title: "Preset") var preset: tempPreset?
+
+    @Parameter(
+        title: "Confirm Before applying",
+        description: "If toggled, you will need to confirm before applying",
+        default: true
+    ) var confirmBeforeApplying: Bool
+
+    static var parameterSummary: some ParameterSummary {
+        When(\ApplyTempPresetIntent.$confirmBeforeApplying, .equalTo, true, {
+            Summary("Applying \(\.$preset)") {
+                \.$confirmBeforeApplying
+            }
+        }, otherwise: {
+            Summary("Immediately applying \(\.$preset)") {
+                \.$confirmBeforeApplying
+            }
+        })
+    }
+
+    @MainActor func perform() async throws -> some ProvidesDialog {
+        do {
+            let presetToApply: tempPreset
+            if let preset = preset {
+                presetToApply = preset
+            } else {
+                presetToApply = try await $preset.requestDisambiguation(
+                    among: intentRequest.fetchAll(),
+                    dialog: "What temp preset would you like ?"
+                )
+            }
+
+            let displayName: String = presetToApply.name
+            if confirmBeforeApplying {
+                try await requestConfirmation(
+                    result: .result(dialog: "Are you sure to applying the temp target \(displayName) ?")
+                )
+            }
+
+            // TODO: enact the temp target
+            let tempTarget = try intentRequest.findTempTarget(presetToApply)
+            let finalTempTargetApply = try intentRequest.enactTempTarget(tempTarget)
+            let displayDetail: String =
+                "the target \(finalTempTargetApply.displayName) is applying during \(finalTempTargetApply.duration) mn"
+            return .result(
+                dialog: IntentDialog(stringLiteral: displayDetail)
+            )
+        } catch {
+            throw error
+        }
+    }
+}

+ 27 - 0
FreeAPS/Sources/Shortcuts/TempPresets/CancelTempPresetIntent.swift

@@ -0,0 +1,27 @@
+import AppIntents
+import Foundation
+
+@available(iOS 16.0, *) struct CancelTempPresetIntent: AppIntent {
+    // Title of the action in the Shortcuts app
+    static var title: LocalizedStringResource = "Cancel a temporary Preset"
+
+    // Description of the action in the Shortcuts app
+    static var description = IntentDescription("Cancel temporary preset.")
+
+    internal var intentRequest: TempPresetsIntentRequest
+
+    init() {
+        intentRequest = TempPresetsIntentRequest()
+    }
+
+    @MainActor func perform() async throws -> some ProvidesDialog {
+        do {
+            try intentRequest.cancelTempTarget()
+            return .result(
+                dialog: IntentDialog(stringLiteral: "Temporary Target canceled")
+            )
+        } catch {
+            throw error
+        }
+    }
+}

+ 43 - 0
FreeAPS/Sources/Shortcuts/TempPresets/ListTempPresetsIntent.swift

@@ -0,0 +1,43 @@
+import AppIntents
+import Foundation
+
+@available(iOS 16.0, *) struct ListTempPresetsIntent: AppIntent {
+    // Title of the action in the Shortcuts app
+    static var title: LocalizedStringResource = "Choose Temporary Presets"
+
+    // Description of the action in the Shortcuts app
+    static var description = IntentDescription(
+        "Allow to list and choose a specific temporary Preset.",
+        categoryName: "Navigation"
+    )
+
+    @Parameter(title: "Preset") var preset: tempPreset?
+
+    static var parameterSummary: some ParameterSummary {
+        Summary("Choose the temp preset  \(\.$preset)")
+    }
+
+    @MainActor func perform() async throws -> some ReturnsValue<tempPreset> {
+        .result(
+            value: preset!
+        )
+    }
+}
+
+@available(iOS 16.0, *) struct tempPresetsQuery: EntityQuery {
+    internal var intentRequest: TempPresetsIntentRequest
+
+    init() {
+        intentRequest = TempPresetsIntentRequest()
+    }
+
+    func entities(for identifiers: [tempPreset.ID]) async throws -> [tempPreset] {
+        let tempTargets = intentRequest.fetchIDs(identifiers)
+        return tempTargets
+    }
+
+    func suggestedEntities() async throws -> [tempPreset] {
+        let tempTargets = intentRequest.fetchAll()
+        return tempTargets
+    }
+}

+ 84 - 0
FreeAPS/Sources/Shortcuts/TempPresets/TempPresetsIntentRequest.swift

@@ -0,0 +1,84 @@
+import CoreData
+import Foundation
+
+@available(iOS 16.0, *) final class TempPresetsIntentRequest: BaseIntentsRequest {
+    enum TempPresetsError: Error {
+        case noTempTargetFound
+        case noDurationDefined
+    }
+
+    private func convert(tt: [TempTarget]) -> [tempPreset] {
+        tt.map { tempPreset.convert($0) }
+    }
+
+    func fetchAll() -> [tempPreset] {
+        convert(tt: tempTargetsStorage.presets())
+    }
+
+    func fetchIDs(_ uuid: [tempPreset.ID]) -> [tempPreset] {
+        let UUIDTempTarget = tempTargetsStorage.presets().filter { uuid.contains(UUID(uuidString: $0.id)!) }
+        return convert(tt: UUIDTempTarget)
+    }
+
+    func fetchOne(_ uuid: tempPreset.ID) -> tempPreset? {
+        let UUIDTempTarget = tempTargetsStorage.presets().filter { UUID(uuidString: $0.id) == uuid }
+        guard let OneTempTarget = UUIDTempTarget.first else { return nil }
+        return tempPreset.convert(OneTempTarget)
+    }
+
+    func findTempTarget(_ tempPreset: tempPreset) throws -> TempTarget {
+        let tempTargetFound = tempTargetsStorage.presets().filter { $0.id == tempPreset.id.uuidString }
+        guard let tempOneTarget = tempTargetFound.first else { throw TempPresetsError.noTempTargetFound }
+        return tempOneTarget
+    }
+
+    func enactTempTarget(_ presetTarget: TempTarget) throws -> TempTarget {
+        var tempTarget = presetTarget
+        tempTarget.createdAt = Date()
+        storage.storeTempTargets([tempTarget])
+
+        coredataContext.performAndWait {
+            var tempTargetsArray = [TempTargetsSlider]()
+            let requestTempTargets = TempTargetsSlider.fetchRequest() as NSFetchRequest<TempTargetsSlider>
+            let sortTT = NSSortDescriptor(key: "date", ascending: false)
+            requestTempTargets.sortDescriptors = [sortTT]
+            try? tempTargetsArray = coredataContext.fetch(requestTempTargets)
+
+            let whichID = tempTargetsArray.first(where: { $0.id == tempTarget.id })
+
+            if whichID != nil {
+                let saveToCoreData = TempTargets(context: self.coredataContext)
+                saveToCoreData.active = true
+                saveToCoreData.date = Date()
+                saveToCoreData.hbt = whichID?.hbt ?? 160
+                saveToCoreData.startDate = Date()
+                saveToCoreData.duration = whichID?.duration ?? 0
+
+                try? self.coredataContext.save()
+            } else {
+                let saveToCoreData = TempTargets(context: self.coredataContext)
+                saveToCoreData.active = false
+                saveToCoreData.date = Date()
+                try? self.coredataContext.save()
+            }
+        }
+
+        return tempTarget
+    }
+
+    func cancelTempTarget() throws {
+        storage.storeTempTargets([TempTarget.cancel(at: Date())])
+        try coredataContext.performAndWait {
+            let saveToCoreData = TempTargets(context: self.coredataContext)
+            saveToCoreData.active = false
+            saveToCoreData.date = Date()
+            try self.coredataContext.save()
+
+            let setHBT = TempTargetsSlider(context: self.coredataContext)
+            setHBT.enabled = false
+            setHBT.date = Date()
+
+            try self.coredataContext.save()
+        }
+    }
+}

+ 31 - 0
FreeAPS/Sources/Shortcuts/TempPresets/tempPresetIntent.swift

@@ -0,0 +1,31 @@
+import AppIntents
+import Foundation
+import Intents
+import Swinject
+
+@available(iOS 16.0, *) struct tempPreset: AppEntity, Identifiable {
+    static var defaultQuery = tempPresetsQuery()
+
+    var id: UUID
+    var name: String
+    var targetTop: Decimal?
+    var targetBottom: Decimal?
+    var duration: Decimal
+
+    var displayRepresentation: DisplayRepresentation {
+        DisplayRepresentation(title: "\(name)")
+    }
+
+    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Presets"
+
+    static func convert(_ tempTarget: TempTarget) -> tempPreset {
+        var tp = tempPreset(
+            id: UUID(uuidString: tempTarget.id)!,
+            name: tempTarget.displayName,
+            duration: tempTarget.duration
+        )
+        tp.targetTop = tempTarget.targetTop
+        tp.targetBottom = tempTarget.targetBottom
+        return tp
+    }
+}