Просмотр исходного кода

Merge branch 'core-data-sync-trio' of github.com:dnzxy/Open-iAPS into trio/settings-refactor

Deniz Cengiz 1 год назад
Родитель
Сommit
fa1c656e4f

+ 54 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -11,6 +11,12 @@
 		0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5822B15939E719628E9FF7C /* SnoozeRootView.swift */; };
 		0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */; };
 		0F7A65FBD2CD8D6477ED4539 /* NotificationsConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E625985B47742D498CB1681A /* NotificationsConfigProvider.swift */; };
+		110AEDE32C5193D200615CC9 /* BolusIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE02C5193D100615CC9 /* BolusIntent.swift */; };
+		110AEDE42C5193D200615CC9 /* BolusIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE12C5193D100615CC9 /* BolusIntentRequest.swift */; };
+		110AEDEB2C51A0AE00615CC9 /* ShortcutsConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE52C51A0AE00615CC9 /* ShortcutsConfigView.swift */; };
+		110AEDEC2C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE72C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift */; };
+		110AEDED2C51A0AE00615CC9 /* ShortcutsConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE82C51A0AE00615CC9 /* ShortcutsConfigProvider.swift */; };
+		110AEDEE2C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */; };
 		17A9D0899046B45E87834820 /* CREditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C8D5F457B5AFF763F8CF3DF /* CREditorProvider.swift */; };
 		19012CDC291D2CB900FB8210 /* LoopStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19012CDB291D2CB900FB8210 /* LoopStats.swift */; };
 		190EBCC429FF136900BA767D /* StatConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190EBCC329FF136900BA767D /* StatConfigDataFlow.swift */; };
@@ -543,6 +549,12 @@
 /* Begin PBXFileReference section */
 		0274EE6439B1C3ED70730D41 /* PumpSettingsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorDataFlow.swift; sourceTree = "<group>"; };
 		0CA3E609094E064C99A4752C /* PreferencesEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorStateModel.swift; sourceTree = "<group>"; };
+		110AEDE02C5193D100615CC9 /* BolusIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusIntent.swift; sourceTree = "<group>"; };
+		110AEDE12C5193D100615CC9 /* BolusIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusIntentRequest.swift; sourceTree = "<group>"; };
+		110AEDE52C51A0AE00615CC9 /* ShortcutsConfigView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigView.swift; sourceTree = "<group>"; };
+		110AEDE72C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigDataFlow.swift; sourceTree = "<group>"; };
+		110AEDE82C51A0AE00615CC9 /* ShortcutsConfigProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigProvider.swift; sourceTree = "<group>"; };
+		110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigStateModel.swift; sourceTree = "<group>"; };
 		12204445D7632AF09264A979 /* PreferencesEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorDataFlow.swift; sourceTree = "<group>"; };
 		19012CDB291D2CB900FB8210 /* LoopStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStats.swift; sourceTree = "<group>"; };
 		190EBCC329FF136900BA767D /* StatConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatConfigDataFlow.swift; sourceTree = "<group>"; };
@@ -1136,6 +1148,34 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		110AEDE22C5193D100615CC9 /* Bolus */ = {
+			isa = PBXGroup;
+			children = (
+				110AEDE02C5193D100615CC9 /* BolusIntent.swift */,
+				110AEDE12C5193D100615CC9 /* BolusIntentRequest.swift */,
+			);
+			path = Bolus;
+			sourceTree = "<group>";
+		};
+		110AEDE62C51A0AE00615CC9 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				110AEDE52C51A0AE00615CC9 /* ShortcutsConfigView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		110AEDEA2C51A0AE00615CC9 /* ShortcutsConfig */ = {
+			isa = PBXGroup;
+			children = (
+				110AEDE62C51A0AE00615CC9 /* View */,
+				110AEDE72C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift */,
+				110AEDE82C51A0AE00615CC9 /* ShortcutsConfigProvider.swift */,
+				110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */,
+			);
+			path = ShortcutsConfig;
+			sourceTree = "<group>";
+		};
 		18B49BC9587A59E3A347C1CD /* View */ = {
 			isa = PBXGroup;
 			children = (
@@ -1289,6 +1329,7 @@
 		3811DE0325C9D31700A708ED /* Modules */ = {
 			isa = PBXGroup;
 			children = (
+				110AEDEA2C51A0AE00615CC9 /* ShortcutsConfig */,
 				DDD163032C4C67B400CD525A /* OverrideConfig */,
 				195D80B22AF696EE00D25097 /* Dynamic */,
 				BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */,
@@ -2247,6 +2288,7 @@
 		CE7CA3422A064973004BE681 /* Shortcuts */ = {
 			isa = PBXGroup;
 			children = (
+				110AEDE22C5193D100615CC9 /* Bolus */,
 				CE1856F32ADC4835007E39C7 /* Carbs */,
 				CE7CA3432A064973004BE681 /* AppShortcuts.swift */,
 				CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */,
@@ -2868,6 +2910,7 @@
 				38DF1786276A73D400B3528F /* TagCloudView.swift in Sources */,
 				38B4F3CD25E5031100E76A18 /* Broadcaster.swift in Sources */,
 				383420D925FFEB3F002D46C1 /* Popup.swift in Sources */,
+				110AEDEE2C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift in Sources */,
 				3811DE3025C9D49500A708ED /* HomeStateModel.swift in Sources */,
 				38BF021725E7CBBC00579895 /* PumpManagerExtensions.swift in Sources */,
 				CEE9A6552BBB418300EB5194 /* CalibrationsProvider.swift in Sources */,
@@ -3045,11 +3088,13 @@
 				190EBCC429FF136900BA767D /* StatConfigDataFlow.swift in Sources */,
 				5A2325582BFCC168003518CA /* NightscoutConnectView.swift in Sources */,
 				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
+				110AEDE42C5193D200615CC9 /* BolusIntentRequest.swift in Sources */,
 				3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,
 				45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */,
 				587DA1F62B77F3DD00B28F8A /* SettingsRowView.swift in Sources */,
 				CE7CA34E2A064973004BE681 /* AppShortcuts.swift in Sources */,
 				38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */,
+				110AEDEB2C51A0AE00615CC9 /* ShortcutsConfigView.swift in Sources */,
 				38DF179027733EAD00B3528F /* SnowScene.swift in Sources */,
 				DD1DB7CC2BECCA1F0048B367 /* BuildDetails.swift in Sources */,
 				1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */,
@@ -3061,6 +3106,7 @@
 				CE7CA3532A064973004BE681 /* tempPresetIntent.swift in Sources */,
 				D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */,
 				38E98A3025F52FF700C0CED0 /* Config.swift in Sources */,
+				110AEDE32C5193D200615CC9 /* BolusIntent.swift in Sources */,
 				DDD1631A2C4C695E00CD525A /* EditOverrideForm.swift in Sources */,
 				CE1856F72ADC4869007E39C7 /* CarbPresetIntentRequest.swift in Sources */,
 				CE1F6DDB2BAE08B60064EB8D /* TidepoolManager.swift in Sources */,
@@ -3130,6 +3176,7 @@
 				98641AF4F92123DA668AB931 /* CREditorRootView.swift in Sources */,
 				BDF34F902C10CF8C00D51995 /* CoreDataStack.swift in Sources */,
 				CEE9A65C2BBB41C800EB5194 /* CalibrationService.swift in Sources */,
+				110AEDED2C51A0AE00615CC9 /* ShortcutsConfigProvider.swift in Sources */,
 				38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */,
 				38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */,
 				CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */,
@@ -3142,6 +3189,7 @@
 				A228DF96647338139F152B15 /* PreferencesEditorDataFlow.swift in Sources */,
 				DDD163182C4C694000CD525A /* OverrideRootView.swift in Sources */,
 				389ECE052601144100D86C4F /* ConcurrentMap.swift in Sources */,
+				110AEDEC2C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift in Sources */,
 				CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */,
 				E4984C5262A90469788754BB /* PreferencesEditorProvider.swift in Sources */,
 				DD399FB31EACB9343C944C4C /* PreferencesEditorStateModel.swift in Sources */,
@@ -3489,8 +3537,10 @@
 				ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				CODE_SIGN_ENTITLEMENTS = FreeAPS/Resources/FreeAPS.entitlements;
+				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = $APP_BUILD_NUMBER;
+				DEVELOPER_TEAM = "$(DEVELOPER_TEAM)";
 				DEVELOPMENT_ASSET_PATHS = "";
 				DEVELOPMENT_TEAM = "$(DEVELOPER_TEAM)";
 				ENABLE_PREVIEWS = YES;
@@ -3512,6 +3562,7 @@
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};
@@ -3527,8 +3578,10 @@
 				ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
 				BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				CODE_SIGN_ENTITLEMENTS = FreeAPS/Resources/FreeAPS.entitlements;
+				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = $APP_BUILD_NUMBER;
+				DEVELOPER_TEAM = "$(DEVELOPER_TEAM)";
 				DEVELOPMENT_ASSET_PATHS = "";
 				DEVELOPMENT_TEAM = "$(DEVELOPER_TEAM)";
 				ENABLE_PREVIEWS = YES;
@@ -3550,6 +3603,7 @@
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};

+ 20 - 0
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -1,5 +1,20 @@
 import Foundation
 
+enum BolusShortcutLimit: String, JSON, CaseIterable, Identifiable {
+    var id: String { rawValue }
+    case notAllowed
+    case limitBolusMax
+
+    var displayName: String {
+        switch self {
+        case .notAllowed:
+            return String(localized: "Not allowed", table: "ShortcutsDetail")
+        case .limitBolusMax:
+            return String(localized: "Max bolus", table: "ShortcutsDetail")
+        }
+    }
+}
+
 struct FreeAPSSettings: JSON, Equatable {
     var units: GlucoseUnits = .mgdL
     var closedLoop: Bool = false
@@ -58,6 +73,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var displayPresets: Bool = true
     var useLiveActivity: Bool = false
     var lockScreenView: LockScreenView = .simple
+    var bolusShortcut: BolusShortcutLimit = .notAllowed
 }
 
 extension FreeAPSSettings: Decodable {
@@ -299,6 +315,10 @@ extension FreeAPSSettings: Decodable {
             settings.lockScreenView = lockScreenView
         }
 
+        if let bolusShortcut = try? container.decode(BolusShortcutLimit.self, forKey: .bolusShortcut) {
+            settings.bolusShortcut = bolusShortcut
+        }
+
         self = settings
     }
 }

+ 2 - 0
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -142,6 +142,8 @@ extension Settings {
                     if HKHealthStore.isHealthDataAvailable() {
                         Text("Apple Health").navigationLink(to: .healthkit, from: self)
                     }
+
+                    Text("Shortcuts", tableName: "ShortcutsDetail").navigationLink(to: .shortcutsConfig, from: self)
                 } header: { Text("Services") }.listRowBackground(Color.chart)
 
                 Section {

+ 5 - 0
FreeAPS/Sources/Modules/ShortcutsConfig/ShortcutsConfigDataFlow.swift

@@ -0,0 +1,5 @@
+enum ShortcutsConfig {
+    enum Config {}
+}
+
+protocol ShortcutsConfigProvider {}

+ 3 - 0
FreeAPS/Sources/Modules/ShortcutsConfig/ShortcutsConfigProvider.swift

@@ -0,0 +1,3 @@
+extension ShortcutsConfig {
+    final class Provider: BaseProvider, ShortcutsConfigProvider {}
+}

+ 36 - 0
FreeAPS/Sources/Modules/ShortcutsConfig/ShortcutsConfigStateModel.swift

@@ -0,0 +1,36 @@
+//
+//  ShortcutsConfigStateModel.swift
+//  FreeAPS
+//
+//  Created by Pierre LAGARDE on 01/05/2024.
+//
+import SwiftUI
+
+extension ShortcutsConfig {
+    final class StateModel: BaseStateModel<Provider> {
+        @Published var allowBolusByShortcuts: Bool = false
+        @Published var maxBolusByShortcuts: BolusShortcutLimit = .notAllowed
+
+        override func subscribe() {
+            subscribeSetting(\.bolusShortcut, on: $maxBolusByShortcuts) {
+                maxBolusByShortcuts = ($0 == .notAllowed) ? .limitBolusMax : $0
+                allowBolusByShortcuts = ($0 != .notAllowed)
+            }
+
+            $allowBolusByShortcuts.receive(on: DispatchQueue.main)
+                .sink { [weak self] value in
+                    if !value {
+                        // the bolus is not allowed
+                        self?.settingsManager.settings.bolusShortcut = .notAllowed
+                    } else {
+                        if let bs = self?.maxBolusByShortcuts {
+                            self?.settingsManager.settings.bolusShortcut = bs
+                        } else {
+                            self?.settingsManager.settings.bolusShortcut = .limitBolusMax
+                        }
+                    }
+                }
+                .store(in: &lifetime)
+        }
+    }
+}

+ 57 - 0
FreeAPS/Sources/Modules/ShortcutsConfig/View/ShortcutsConfigView.swift

@@ -0,0 +1,57 @@
+import Combine
+import SwiftUI
+import Swinject
+import UIKit
+
+extension ShortcutsConfig {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @StateObject var state = StateModel()
+
+        var body: some View {
+            Form {
+                Section(header: Text("Shortcuts", tableName: "ShortcutsDetail")) {
+                    Text(
+                        "The application lets you create automations using shortcuts. Go to the Shortcuts application to create new automations.",
+                        tableName: "ShortcutsDetail"
+                    )
+                    Button(String(localized: "Open Shortcuts app", table: "ShortcutsDetail")) {
+                        openShortcutsApp()
+                    }
+                }
+
+                Section(header: Text("Options", tableName: "ShorcutsDetail")) {
+                    Toggle(
+                        String(localized: "Allow bolusing with Shortcuts", table: "ShortcutsDetail"),
+                        isOn: $state.allowBolusByShortcuts
+                    )
+                }
+            }
+            .onAppear(perform: configureView)
+            .navigationTitle(String(localized: "Shortcuts config", table: "ShortcutsDetail"))
+            .navigationBarTitleDisplayMode(.automatic)
+        }
+
+        private func openShortcutsApp() {
+            let shortcutsURL = URL(string: "shortcuts://")!
+
+            if UIApplication.shared.canOpenURL(shortcutsURL) {
+                UIApplication.shared.open(shortcutsURL, options: [:], completionHandler: { success in
+                    if !success {
+                        state.router.alertMessage
+                            .send(MessageContent(
+                                content: String(localized: "Unable to open the app", table: "ShortcutsDetail"),
+                                type: .warning
+                            ))
+                    }
+                })
+            } else {
+                router.alertMessage
+                    .send(MessageContent(
+                        content: String(localized: "Unable to open the app", table: "ShortcutsDetail"),
+                        type: .warning
+                    ))
+            }
+        }
+    }
+}

+ 3 - 0
FreeAPS/Sources/Router/Screen.swift

@@ -34,6 +34,7 @@ enum Screen: Identifiable, Hashable {
     case bolusCalculatorConfig
     case dynamicISF
     case calibrations
+    case shortcutsConfig
 
     var id: Int { String(reflecting: self).hashValue }
 }
@@ -105,6 +106,8 @@ extension Screen {
             Dynamic.RootView(resolver: resolver)
         case .calibrations:
             Calibrations.RootView(resolver: resolver)
+        case .shortcutsConfig:
+            ShortcutsConfig.RootView(resolver: resolver)
         }
     }
 

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

@@ -4,6 +4,13 @@ import Foundation
 @available(iOS 16.0, *) struct AppShortcuts: AppShortcutsProvider {
     @AppShortcutsBuilder static var appShortcuts: [AppShortcut] {
         AppShortcut(
+            intent: BolusIntent(),
+            phrases: [
+                "\(.applicationName) bolus",
+                "Enacts a \(.applicationName) Bolus"
+            ]
+        )
+        AppShortcut(
             intent: ApplyTempPresetIntent(),
             phrases: [
                 "Activate \(.applicationName) temporary target ?",

+ 76 - 0
FreeAPS/Sources/Shortcuts/Bolus/BolusIntent.swift

@@ -0,0 +1,76 @@
+import AppIntents
+import Foundation
+import Intents
+import Swinject
+
+@available(iOS 16.0,*) struct BolusIntent: AppIntent {
+    // Title of the action in the Shortcuts app
+    static var title = LocalizedStringResource("Enact Bolus", table: "ShortcutsDetail")
+
+    // Description of the action in the Shortcuts app
+    static var description = IntentDescription(.init("Allow to send a bolus to the app", table: "ShortcutsDetail"))
+
+    internal var bolusRequest: BolusIntentRequest
+
+    init() {
+        bolusRequest = BolusIntentRequest()
+    }
+
+    @Parameter(
+        title: LocalizedStringResource("Amount", table: "ShortcutsDetail"),
+        description: LocalizedStringResource("Bolus amount in U", table: "ShortcutsDetail"),
+        controlStyle: .field,
+        /// The 200 upperBound does nothing here, the true max is set based on pump max
+        /// An upperBound is specificed so that we can usethe lowerBound of 0, which ensures no negatives are allowed
+        /// A preferred approach would be to just block negatives and not specify an upperBound here, since it is implemented elsewhere
+        inclusiveRange: (lowerBound: 0, upperBound: 200),
+        requestValueDialog: IntentDialog(LocalizedStringResource(
+            "Bolus amount (units of insulin)?",
+            table: "ShortcutsDetail"
+        ))
+    ) var bolusQuantity: Double
+
+    @Parameter(
+        title: LocalizedStringResource("Confirm Before applying", table: "ShortcutsDetail"),
+        description: LocalizedStringResource("If toggled, you will need to confirm before applying.", table: "ShortcutsDetail"),
+        default: true
+    ) var confirmBeforeApplying: Bool
+
+    static var parameterSummary: some ParameterSummary {
+        When(\.$confirmBeforeApplying, .equalTo, true, {
+            Summary("Applying \(\.$bolusQuantity) U", table: "ShortcutsDetail") {
+                \.$confirmBeforeApplying
+            }
+        }, otherwise: {
+            Summary("Immediately applying \(\.$bolusQuantity) U", table: "ShortcutsDetail") {
+                \.$confirmBeforeApplying
+            }
+        })
+    }
+
+    @MainActor func perform() async throws -> some ProvidesDialog {
+        do {
+            let amount: Double = bolusQuantity
+
+            let bolusFormatted = amount.formatted()
+            if confirmBeforeApplying {
+                try await requestConfirmation(
+                    result: .result(
+                        dialog: IntentDialog(LocalizedStringResource(
+                            "Are you sure you want to bolus \(bolusFormatted) U of insulin?",
+                            table: "ShortcutsDetail"
+                        ))
+                    )
+                )
+            }
+
+            let finalBolusDisplay = try await bolusRequest.bolus(amount)
+            return .result(
+                dialog: IntentDialog(finalBolusDisplay)
+            )
+
+        } catch {
+            throw error
+        }
+    }
+}

+ 33 - 0
FreeAPS/Sources/Shortcuts/Bolus/BolusIntentRequest.swift

@@ -0,0 +1,33 @@
+import Combine
+import CoreData
+import Foundation
+
+@available(iOS 16.0,*) final class BolusIntentRequest: BaseIntentsRequest {
+    func bolus(_ bolusAmount: Double) async throws -> LocalizedStringResource {
+        var bolusQuantity: Decimal = 0
+        switch settingsManager.settings.bolusShortcut {
+        // Block boluses if they are disabled
+        case .notAllowed:
+            return LocalizedStringResource(
+                "Bolusing via Shortcuts is disabled in Trio settings.",
+                table: "ShortcutsDetail"
+            )
+
+        // Block any bolus attempted if it is larger than the max bolus in settings
+        case .limitBolusMax:
+            if Decimal(bolusAmount) > settingsManager.pumpSettings.maxBolus {
+                return LocalizedStringResource(
+                    "The bolus cannot be larger than the pump setting max bolus (\(settingsManager.pumpSettings.maxBolus.description)).",
+                    table: "ShortcutsDetail"
+                )
+            } else {
+                bolusQuantity = apsManager.roundBolus(amount: Decimal(bolusAmount))
+            }
+            await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false)
+            return LocalizedStringResource(
+                "A bolus command of \(bolusQuantity.formatted()) U of insulin was sent.",
+                table: "ShortcutsDetail"
+            )
+        }
+    }
+}

+ 2 - 2
Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -1,5 +1,5 @@
 {
-  "originHash" : "f5c836c216c4ca7d356e3777e58d6d4f9502b03f3974891349eb775f4c4cf750",
+  "originHash" : "59ac7eba66375d6eb406e758cb0b9964f4b3b0ae45c5665596f00384c32262b9",
   "pins" : [
     {
       "identity" : "cryptoswift",
@@ -49,7 +49,7 @@
     {
       "identity" : "swiftcharts",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/ivanschuetz/SwiftCharts",
+      "location" : "https://github.com/ivanschuetz/SwiftCharts.git",
       "state" : {
         "branch" : "master",
         "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2"

+ 6 - 3
scripts/capture-build-details.sh

@@ -20,8 +20,8 @@ else
     # Capture the current date and write it to BuildDetails.plist
     plutil -replace com-trio-build-date -string "$(date)" "${info_plist_path}"
 
-    # Retrieve the current branch
-    git_branch=$(git symbolic-ref --short -q HEAD)
+    # Retrieve the current branch, if available
+    git_branch=$(git symbolic-ref --short -q HEAD || echo "")
 
     # Attempt to retrieve the current tag
     git_tag=$(git describe --tags --exact-match 2>/dev/null || echo "")
@@ -29,8 +29,11 @@ else
     # Retrieve the current SHA of the latest commit
     git_commit_sha=$(git log -1 --format="%h" --abbrev=7)
 
-    # Determine the branch or tag information
+    # Determine the branch or tag information, or fallback to SHA if in detached state
     git_branch_or_tag="${git_branch:-${git_tag}}"
+    if [ -z "${git_branch_or_tag}" ]; then
+        git_branch_or_tag="detached"
+    fi
 
     # Update BuildDetails.plist with the branch or tag information
     plutil -replace com-trio-branch -string "${git_branch_or_tag}" "${info_plist_path}"