Bläddra i källkod

Add permission request screens for notifications and bluetooth

Deniz Cengiz 1 år sedan
förälder
incheckning
e51a6cb0ea

+ 8 - 0
Trio.xcodeproj/project.pbxproj

@@ -660,6 +660,8 @@
 		DDF847E62C5D66490049BB3B /* AddMealPresetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E52C5D66490049BB3B /* AddMealPresetView.swift */; };
 		DDF847E82C5DABA30049BB3B /* WatchConfigAppleWatchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E72C5DABA30049BB3B /* WatchConfigAppleWatchView.swift */; };
 		DDF847EA2C5DABAC0049BB3B /* WatchConfigGarminView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF847E92C5DABAC0049BB3B /* WatchConfigGarminView.swift */; };
+		DDFF202F2DB1D14500AB8A96 /* NotificationPermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */; };
+		DDFF20312DB1D15500AB8A96 /* BluetoothPermissionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */; };
 		E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFD27368630002FF094 /* ServiceAssembly.swift */; };
 		E00EEC0427368630002FF094 /* SecurityAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFE27368630002FF094 /* SecurityAssembly.swift */; };
 		E00EEC0527368630002FF094 /* StorageAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFF27368630002FF094 /* StorageAssembly.swift */; };
@@ -1457,6 +1459,8 @@
 		DDF847E52C5D66490049BB3B /* AddMealPresetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddMealPresetView.swift; sourceTree = "<group>"; };
 		DDF847E72C5DABA30049BB3B /* WatchConfigAppleWatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConfigAppleWatchView.swift; sourceTree = "<group>"; };
 		DDF847E92C5DABAC0049BB3B /* WatchConfigGarminView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConfigGarminView.swift; sourceTree = "<group>"; };
+		DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionStepView.swift; sourceTree = "<group>"; };
+		DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothPermissionStepView.swift; sourceTree = "<group>"; };
 		E00EEBFD27368630002FF094 /* ServiceAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceAssembly.swift; sourceTree = "<group>"; };
 		E00EEBFE27368630002FF094 /* SecurityAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurityAssembly.swift; sourceTree = "<group>"; };
 		E00EEBFF27368630002FF094 /* StorageAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageAssembly.swift; sourceTree = "<group>"; };
@@ -2780,6 +2784,8 @@
 		BD47FDD52D8B64AE0043966B /* OnboardingSteps */ = {
 			isa = PBXGroup;
 			children = (
+				DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */,
+				DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */,
 				DD4A00222DAEF5CD00AB7387 /* AlgorithmSettings */,
 				DDBD53FB2DAA903100F940A6 /* OverviewStepView.swift */,
 				DDF691362DA30332008BF16C /* StartupGuideStepView.swift */,
@@ -4190,6 +4196,7 @@
 				3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */,
 				3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */,
 				BD249D8C2D42FC2C00412DEB /* GlucoseDistributionChart.swift in Sources */,
+				DDFF20312DB1D15500AB8A96 /* BluetoothPermissionStepView.swift in Sources */,
 				38E87408274F9AD000975559 /* UserNotificationsManager.swift in Sources */,
 				DD3F1F902D9E153F00DCE7B3 /* NightscoutImportStepView.swift in Sources */,
 				CE82E02528E867BA00473A9C /* AlertStorage.swift in Sources */,
@@ -4265,6 +4272,7 @@
 				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */,
 				DDA6E3222D25901100C2988C /* TempTargetHelpView.swift in Sources */,
 				58645B9D2CA2D275008AFCE7 /* DeterminationSetup.swift in Sources */,
+				DDFF202F2DB1D14500AB8A96 /* NotificationPermissionStepView.swift in Sources */,
 				491D6FBD2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift in Sources */,
 				491D6FBE2D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift in Sources */,
 				491D6FBF2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift in Sources */,

+ 12 - 0
Trio/Resources/Assets.xcassets/logo.bluetooth.capsule.portrait.fill.symbolset/Contents.json

@@ -0,0 +1,12 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "symbols" : [
+    {
+      "filename" : "logo.bluetooth.capsule.portrait.fill.svg",
+      "idiom" : "universal"
+    }
+  ]
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 196 - 0
Trio/Resources/Assets.xcassets/logo.bluetooth.capsule.portrait.fill.symbolset/logo.bluetooth.capsule.portrait.fill.svg


+ 57 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -4453,6 +4453,9 @@
         }
       }
     },
+    "“Trio” Would Like to Use Bluetooth" : {
+
+    },
     "(" : {
       "localizations" : {
         "bg" : {
@@ -31169,6 +31172,9 @@
         }
       }
     },
+    "Allow" : {
+
+    },
     "Allow Bolusing with Shortcuts" : {
       "localizations" : {
         "bg" : {
@@ -31470,6 +31476,9 @@
         }
       }
     },
+    "Allow Notifications" : {
+
+    },
     "Allow SMB for 6 hrs after a carb entry." : {
       "localizations" : {
         "bg" : {
@@ -32887,6 +32896,9 @@
         }
       }
     },
+    "Allow Trio to use Bluetooth to communicate with your insulin pump and CGM." : {
+
+    },
     "Allow Upload of Statistics to NS" : {
       "comment" : "Option in preferences",
       "extractionState" : "manual",
@@ -39032,6 +39044,9 @@
         }
       }
     },
+    "Authorize Trio to send notifications and use Bluetooth. You must allow both for Trio to work properly." : {
+
+    },
     "Auto-sensitivity (Autosens) adjusts insulin delivery based on observed sensitivity or resistance." : {
 
     },
@@ -43539,6 +43554,9 @@
         }
       }
     },
+    "Be warned of connectivity or looping issues." : {
+
+    },
     "Because Trio utilizes SMBs and UAM SMBs to help you reach your target glucose and other AID systems do not bolus for COB the same way Trio does, this is initially set to below the full calculated amount (80%). When SMBs and UAM SMBs are enabled, you may find your current CR results in lows and needs to be increased before you increase this setting closer to or at 100%." : {
       "localizations" : {
         "bg" : {
@@ -44056,6 +44074,12 @@
         }
       }
     },
+    "Bluetooth" : {
+
+    },
+    "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices." : {
+
+    },
     "Bluetooth Power Off" : {
       "comment" : "Bluetooth Power Off",
       "extractionState" : "manual",
@@ -58973,6 +58997,9 @@
         }
       }
     },
+    "Connect to your insulin pump so Trio can send dosing commands and stay active in the background." : {
+
+    },
     "Connected" : {
 
     },
@@ -77745,6 +77772,9 @@
         }
       }
     },
+    "Don’t Allow" : {
+
+    },
     "Don't Disable" : {
       "comment" : "Option to keep SMB enabled",
       "localizations" : {
@@ -81519,6 +81549,9 @@
         }
       }
     },
+    "Enable device connectivity" : {
+
+    },
     "Enable Dynamic CR" : {
       "comment" : "Headline Enable Dynamic CR",
       "extractionState" : "manual",
@@ -144063,6 +144096,9 @@
         }
       }
     },
+    "Notifications and Bluetooth permissions are handled to your liking." : {
+
+    },
     "Notifications give you important Trio information without requiring you to open the app." : {
       "localizations" : {
         "bg" : {
@@ -151371,6 +151407,9 @@
         }
       }
     },
+    "Permission Requests" : {
+
+    },
     "Persist sensordata" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -158541,6 +158580,12 @@
         }
       }
     },
+    "Receive glucose readings every 5 minutes from your CGM to keep the loop running." : {
+
+    },
+    "Receive optional real‑time low/high glucose alerts." : {
+
+    },
     "Received unexpected error in %@ from %@: %@" : {
       "localizations" : {
         "bg" : {
@@ -165494,6 +165539,9 @@
         }
       }
     },
+    "See a badge count when you need a carb correction." : {
+
+    },
     "See a live preview of your contact image design at the top of the screen. Changes made to styles, layouts, or settings are instantly reflected." : {
       "localizations" : {
         "bg" : {
@@ -203726,6 +203774,9 @@
     "Trio can automatically adapt insulin delivery based on inputs and glucose forecasts. Your algorithm settings play a major part in accurate and effective dosing." : {
 
     },
+    "Trio can notify of different events when you use it. You must Trio to send you notifications to work properly." : {
+
+    },
     "Trio collects the app's state on crash, device, iOS and general system info, and a stack trace." : {
 
     },
@@ -205652,6 +205703,9 @@
         }
       }
     },
+    "Trio requires Bluetooth to function as a (hybrid) closed‑loop system." : {
+
+    },
     "Trio State Result" : {
       "localizations" : {
         "bg" : {
@@ -219685,6 +219739,9 @@
         }
       }
     },
+    "You can change these permissions any time in the iOS Settings app." : {
+
+    },
     "You can connect Trio to seamlessly upload and manage your diabetes data on Tidepool." : {
       "localizations" : {
         "bg" : {

+ 9 - 0
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -13,6 +13,8 @@ extension Onboarding {
         @ObservationIgnored @Injected() var broadcaster: Broadcaster!
         @ObservationIgnored @Injected() var keychain: Keychain!
         @ObservationIgnored @Injected() var nightscoutManager: NightscoutManager!
+        @ObservationIgnored @Injected() var notificationsManager: UserNotificationsManager!
+        @ObservationIgnored @Injected() var bluetoothManager: BluetoothStateManager!
 
         private let settingsProvider = PickerSettingsProvider.shared
 
@@ -124,6 +126,13 @@ extension Onboarding {
         var resistanceLowersTarget: Bool = false
         var halfBasalTarget: Decimal = 160
 
+        // MARK: - Permission Requests
+
+        var hasNotificationsGranted = false
+
+        var shouldDisplayBluetoothRequestAlert: Bool = false
+        var hasBluetoothGranted = false
+
         // MARK: - Subscribe
 
         override func subscribe() {

+ 31 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -235,6 +235,15 @@ struct OnboardingStepContent: View {
                                     .resizable()
                                     .scaledToFit()
                                     .frame(width: 60, height: 60)
+                            } else if currentStep == .bluetooth {
+                                Image(currentStep.iconName)
+                                    .font(.system(size: 40))
+                                    .foregroundColor(currentStep.accentColor)
+                                    .frame(width: 60, height: 60)
+                                    .background(
+                                        Circle()
+                                            .fill(currentStep.accentColor.opacity(0.2))
+                                    )
                             } else {
                                 Image(systemName: currentStep.iconName)
                                     .font(.system(size: 40))
@@ -300,6 +309,14 @@ struct OnboardingStepContent: View {
                             AlgorithmSubstepView(state: state, substep: currentSMBSubstep)
                         case .targetBehavior:
                             AlgorithmSubstepView(state: state, substep: currentTargetBehaviorSubstep)
+                        case .notifications:
+                            NotificationPermissionStepView()
+                        case .bluetooth:
+                            BluetoothPermissionStepView(
+                                state: state,
+                                bluetoothManager: state.bluetoothManager,
+                                currentStep: $currentStep
+                            )
                         case .completed:
                             CompletedStepView()
                         }
@@ -500,6 +517,20 @@ struct OnboardingNavigationButtons: View {
                 currentTargetBehaviorSubstep = .highTempTargetRaisesSensitivity
             }
 
+        case .notifications:
+            currentTargetBehaviorSubstep = .halfBasalTarget
+            if let next = currentStep.next {
+                DispatchQueue.main.async {
+                    state.notificationsManager.requestNotificationPermissions { granted in
+                        state.hasNotificationsGranted = granted
+                        currentStep = next
+                    }
+                }
+            }
+
+        case .bluetooth:
+            state.shouldDisplayBluetoothRequestAlert = true
+
         case .completed:
             state.saveOnboardingData()
             onboardingManager.completeOnboarding()

+ 142 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/BluetoothPermissionStepView.swift

@@ -0,0 +1,142 @@
+//
+//  BluetoothPermissionStepView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 18.04.25.
+//
+import CoreBluetooth
+import SwiftUI
+import UIKit
+
+struct BluetoothPermissionStepView: View {
+    @Bindable var state: Onboarding.StateModel
+    var bluetoothManager: BluetoothStateManager
+    var currentStep: Binding<OnboardingStep>
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Enable device connectivity")
+                .font(.title3)
+                .bold()
+                .multilineTextAlignment(.leading)
+
+            Text("Trio requires Bluetooth to function as a (hybrid) closed‑loop system.")
+                .font(.body)
+                .multilineTextAlignment(.leading)
+                .foregroundColor(Color.secondary)
+                .padding(.bottom)
+
+            VStack(alignment: .leading, spacing: 20) {
+                HStack(spacing: 12) {
+                    Image(systemName: "keyboard.onehanded.left.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text(
+                        "Connect to your insulin pump so Trio can send dosing commands and stay active in the background."
+                    )
+                    .font(.body)
+                    .foregroundColor(.primary)
+                }
+
+                HStack(spacing: 12) {
+                    Image(systemName: "sensor.tag.radiowaves.forward.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text("Receive glucose readings every 5 minutes from your CGM to keep the loop running.")
+                        .font(.body)
+                        .foregroundColor(.primary)
+                }
+            }
+
+            Text("You can change these permissions any time in the iOS Settings app.")
+                .font(.footnote)
+                .multilineTextAlignment(.leading)
+                .foregroundColor(Color.secondary)
+                .padding(.top)
+        }
+        .padding(.horizontal)
+        .background(
+            SystemAlert(
+                isPresented: $state.shouldDisplayBluetoothRequestAlert,
+                title: String(localized: "“Trio” Would Like to Use Bluetooth"),
+                message: String(
+                    localized: "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices."
+                ),
+                allowTitle: String(localized: "Allow"),
+                denyTitle: String(localized: "Don’t Allow"),
+                onAllow: {
+                    bluetoothManager.authorizeBluetooth { auth in
+                        DispatchQueue.main.async {
+                            state.hasBluetoothGranted = (auth == .authorized)
+                            state.shouldDisplayBluetoothRequestAlert = false
+                            if let next = currentStep.wrappedValue.next {
+                                currentStep.wrappedValue = next
+                            }
+                        }
+                    }
+                },
+                onDeny: {
+                    state.hasBluetoothGranted = false
+                    state.shouldDisplayBluetoothRequestAlert = false
+                    if let next = currentStep.wrappedValue.next {
+                        currentStep.wrappedValue = next
+                    }
+                }
+            )
+        )
+    }
+}
+
+/// Presents a real UIAlertController, pinned to the system's own style
+///
+/// Why use this?
+/// SwiftUI’s built‑in .alert will always inherit the color scheme of its host view (in our case, we have forced .dark for the entire onboarding screen).
+/// There’s no way to tell SwiftUI “use the system setting here only for this one alert.”
+/// The workaround is to present a plain UIKit UIAlertController ourself, in its own representable, and explicitly tell it to use the system’s interface style instead of inheriting our forced dark mode.
+/// We enforce usage of the system's interface style by setting its overrideUserInterfaceStyle to whatever the device is actually using (.light or .dark).
+struct SystemAlert: UIViewControllerRepresentable {
+    @Binding var isPresented: Bool
+
+    let title: String
+    let message: String
+    let allowTitle: String
+    let denyTitle: String
+
+    /// called after Allow or Deny
+    let onAllow: () -> Void
+    let onDeny: () -> Void
+
+    func makeUIViewController(context _: Context) -> UIViewController {
+        // empty container
+        UIViewController()
+    }
+
+    func updateUIViewController(_ uiVC: UIViewController, context _: Context) {
+        guard isPresented, uiVC.presentedViewController == nil else { return }
+
+        let alert = UIAlertController(
+            title: title,
+            message: message,
+            preferredStyle: .alert
+        )
+
+        // force it back to the "real" system style
+        let systemStyle = UIScreen.main.traitCollection.userInterfaceStyle
+        alert.overrideUserInterfaceStyle = systemStyle
+
+        alert.addAction(.init(title: denyTitle, style: .cancel) { _ in
+            isPresented = false
+            onDeny()
+        })
+        alert.addAction(.init(title: allowTitle, style: .default) { _ in
+            isPresented = false
+            onAllow()
+        })
+
+        uiVC.present(alert, animated: true)
+    }
+}

+ 8 - 5
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/CompletedStepView.swift

@@ -23,7 +23,6 @@ struct CompletedStepView: View {
                 completedItemsView(
                     stepIndex: 1,
                     title: String(localized: "Prepare Trio"),
-                    steps: [.diagnostics, .nightscout, .unitSelection],
                     description: String(
                         localized: "App diagnostics sharing, Nightscout setup, and unit and pump model selection are all complete."
                     )
@@ -34,7 +33,6 @@ struct CompletedStepView: View {
                 completedItemsView(
                     stepIndex: 2,
                     title: String(localized: "Therapy Settings"),
-                    steps: [.glucoseTarget, .basalRates, .carbRatio, .insulinSensitivity],
                     description: String(
                         localized: "Glucose target, basal rates, carb ratios, and insulin sensitivity match your needs."
                     )
@@ -45,7 +43,6 @@ struct CompletedStepView: View {
                 completedItemsView(
                     stepIndex: 3,
                     title: String(localized: "Delivery Limits"),
-                    steps: [.deliveryLimits],
                     description: String(
                         localized: "Safety boundaries for insulin delivery and carb entries are set to help Trio keep you safe."
                     )
@@ -56,9 +53,16 @@ struct CompletedStepView: View {
                 completedItemsView(
                     stepIndex: 4,
                     title: String(localized: "Algorithm Settings"),
-                    steps: [.autosensSettings, .smbSettings, .targetBehavior],
                     description: String(localized: "Trio’s algorithm features are customized to fit your preferences and needs.")
                 )
+
+                Divider()
+
+                completedItemsView(
+                    stepIndex: 5,
+                    title: String(localized: "Permission Requests"),
+                    description: String(localized: "Notifications and Bluetooth permissions are handled to your liking.")
+                )
             }
             .padding()
             .background(Color.green.opacity(0.1))
@@ -77,7 +81,6 @@ struct CompletedStepView: View {
     @ViewBuilder private func completedItemsView(
         stepIndex: Int,
         title: String,
-        steps _: [OnboardingStep],
         description: String
     ) -> some View {
         VStack(alignment: .leading, spacing: 10) {

+ 66 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/NotificationPermissionStepView.swift

@@ -0,0 +1,66 @@
+//
+//  NotificationPermissionStepView.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 18.04.25.
+//
+import SwiftUI
+import UserNotifications
+
+struct NotificationPermissionStepView: View {
+    var body: some View {
+        VStack(alignment: .leading, spacing: 20) {
+            Text("Allow Notifications")
+                .font(.title3)
+                .bold()
+                .multilineTextAlignment(.leading)
+
+            Text("Trio can notify of different events when you use it. You must Trio to send you notifications to work properly.")
+                .font(.body)
+                .multilineTextAlignment(.leading)
+                .foregroundColor(Color.secondary)
+                .padding(.bottom)
+
+            VStack(alignment: .leading, spacing: 20) {
+                HStack(spacing: 12) {
+                    Image(systemName: "ellipsis.message.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text("Receive optional real‑time low/high glucose alerts.")
+                        .font(.body)
+                        .foregroundColor(.primary)
+                }
+
+                HStack(spacing: 12) {
+                    Image(systemName: "exclamationmark.triangle.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text("Be warned of connectivity or looping issues.")
+                        .font(.body)
+                        .foregroundColor(.primary)
+                }
+
+                HStack(spacing: 12) {
+                    Image(systemName: "app.badge.fill")
+                        .font(.system(size: 24))
+                        .foregroundColor(Color.bgDarkBlue)
+                        .frame(width: 44, height: 44)
+                        .background(Circle().fill(Color.primary.opacity(0.8)))
+                    Text("See a badge count when you need a carb correction.")
+                        .font(.body)
+                        .foregroundColor(.primary)
+                }
+            }
+
+            Text("You can change these permissions any time in the iOS Settings app.")
+                .font(.footnote)
+                .multilineTextAlignment(.leading)
+                .foregroundColor(Color.secondary)
+                .padding(.top)
+        }.padding(.horizontal)
+    }
+}

+ 11 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/OverviewStepView.swift

@@ -55,6 +55,17 @@ struct OverviewStepView: View {
                         localized: "Customize Trio’s algorithm features. Most users start with the recommended settings."
                     )
                 )
+
+                Divider()
+
+                overviewItem(
+                    stepIndex: 5,
+                    title: String(localized: "Permission Requests"),
+                    duration: "1",
+                    description: String(
+                        localized: "Authorize Trio to send notifications and use Bluetooth. You must allow both for Trio to work properly."
+                    )
+                )
             }
             .padding()
             .background(Color.chart.opacity(0.65))

+ 16 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift

@@ -23,6 +23,8 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
     case autosensSettings
     case smbSettings
     case targetBehavior
+    case notifications
+    case bluetooth
     case completed
 
     var id: Int { rawValue }
@@ -69,6 +71,10 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return String(localized: "Super Micro Bolus")
         case .targetBehavior:
             return String(localized: "Target Behavior")
+        case .notifications:
+            return String(localized: "Notifications")
+        case .bluetooth:
+            return String(localized: "Bluetooth")
         case .completed:
             return String(localized: "All Set!")
         }
@@ -137,6 +143,10 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return String(
                 localized: "Target Behavior allows you to adjust how temporary targets influence ISF, basal, and auto-targeting based on sensitivity or resistance."
             )
+        case .notifications:
+            return "Allow Trio to send you Notifications. These may include alerts, sounds, and icon badges."
+        case .bluetooth:
+            return String(localized: "Allow Trio to use Bluetooth to communicate with your insulin pump and CGM.")
         case .completed:
             return String(
                 localized: "Great job! You've completed the initial setup of Trio. You can always adjust these settings later in the app."
@@ -177,6 +187,10 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return "bolt.fill"
         case .targetBehavior:
             return "gyroscope"
+        case .notifications:
+            return "bell.badge.fill"
+        case .bluetooth:
+            return "logo.bluetooth.capsule.portrait.fill"
         case .completed:
             return "checkmark.circle.fill"
         }
@@ -203,10 +217,12 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
         switch self {
         case .algorithmSettings,
              .autosensSettings,
+             .bluetooth,
              .completed,
              .deliveryLimits,
              .diagnostics,
              .nightscout,
+             .notifications,
              .overview,
              .smbSettings,
              .startupGuide,

+ 16 - 11
Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift

@@ -8,7 +8,9 @@ import Swinject
 import UIKit
 import UserNotifications
 
-protocol UserNotificationsManager {}
+protocol UserNotificationsManager {
+    func requestNotificationPermissions(completion: @escaping (Bool) -> Void)
+}
 
 enum GlucoseSourceKey: String {
     case transmitterBattery
@@ -88,7 +90,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         broadcaster.register(BolusFailureObserver.self, observer: self)
         broadcaster.register(pumpNotificationObserver.self, observer: self)
         broadcaster.register(alertMessageNotificationObserver.self, observer: self)
-        requestNotificationPermissionsIfNeeded()
+//        requestNotificationPermissionsIfNeeded()
         Task {
             await sendGlucoseNotification()
         }
@@ -384,20 +386,23 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         return body
     }
 
-    private func requestNotificationPermissionsIfNeeded() {
-        center.getNotificationSettings { settings in
-            debug(.service, "UNUserNotificationCenter.authorizationStatus: \(String(describing: settings.authorizationStatus))")
-            if ![.authorized, .provisional].contains(settings.authorizationStatus) {
-                self.requestNotificationPermissions()
-            }
-        }
-    }
+//    private func requestNotificationPermissionsIfNeeded() {
+//        center.getNotificationSettings { settings in
+//            debug(.service, "UNUserNotificationCenter.authorizationStatus: \(String(describing: settings.authorizationStatus))")
+//            if ![.authorized, .provisional].contains(settings.authorizationStatus) {
+//                self.requestNotificationPermissions()
+//            }
+//        }
+//    }
 
-    private func requestNotificationPermissions() {
+    func requestNotificationPermissions(completion: @escaping (Bool) -> Void) {
         debug(.service, "requestNotificationPermissions")
         center.requestAuthorization(options: [.badge, .sound, .alert]) { granted, error in
             if granted {
                 debug(.service, "requestNotificationPermissions was granted")
+                DispatchQueue.main.async {
+                    completion(granted)
+                }
             } else {
                 warning(.service, "requestNotificationPermissions failed", error: error)
             }