Explorar el Código

Add permission request screens for notifications and bluetooth

Deniz Cengiz hace 1 año
padre
commit
e51a6cb0ea

+ 8 - 0
Trio.xcodeproj/project.pbxproj

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

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 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" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -31169,6 +31172,9 @@
         }
         }
       }
       }
     },
     },
+    "Allow" : {
+
+    },
     "Allow Bolusing with Shortcuts" : {
     "Allow Bolusing with Shortcuts" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -31470,6 +31476,9 @@
         }
         }
       }
       }
     },
     },
+    "Allow Notifications" : {
+
+    },
     "Allow SMB for 6 hrs after a carb entry." : {
     "Allow SMB for 6 hrs after a carb entry." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -32887,6 +32896,9 @@
         }
         }
       }
       }
     },
     },
+    "Allow Trio to use Bluetooth to communicate with your insulin pump and CGM." : {
+
+    },
     "Allow Upload of Statistics to NS" : {
     "Allow Upload of Statistics to NS" : {
       "comment" : "Option in preferences",
       "comment" : "Option in preferences",
       "extractionState" : "manual",
       "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." : {
     "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%." : {
     "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" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -44056,6 +44074,12 @@
         }
         }
       }
       }
     },
     },
+    "Bluetooth" : {
+
+    },
+    "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices." : {
+
+    },
     "Bluetooth Power Off" : {
     "Bluetooth Power Off" : {
       "comment" : "Bluetooth Power Off",
       "comment" : "Bluetooth Power Off",
       "extractionState" : "manual",
       "extractionState" : "manual",
@@ -58973,6 +58997,9 @@
         }
         }
       }
       }
     },
     },
+    "Connect to your insulin pump so Trio can send dosing commands and stay active in the background." : {
+
+    },
     "Connected" : {
     "Connected" : {
 
 
     },
     },
@@ -77745,6 +77772,9 @@
         }
         }
       }
       }
     },
     },
+    "Don’t Allow" : {
+
+    },
     "Don't Disable" : {
     "Don't Disable" : {
       "comment" : "Option to keep SMB enabled",
       "comment" : "Option to keep SMB enabled",
       "localizations" : {
       "localizations" : {
@@ -81519,6 +81549,9 @@
         }
         }
       }
       }
     },
     },
+    "Enable device connectivity" : {
+
+    },
     "Enable Dynamic CR" : {
     "Enable Dynamic CR" : {
       "comment" : "Headline Enable Dynamic CR",
       "comment" : "Headline Enable Dynamic CR",
       "extractionState" : "manual",
       "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." : {
     "Notifications give you important Trio information without requiring you to open the app." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {
@@ -151371,6 +151407,9 @@
         }
         }
       }
       }
     },
     },
+    "Permission Requests" : {
+
+    },
     "Persist sensordata" : {
     "Persist sensordata" : {
       "extractionState" : "manual",
       "extractionState" : "manual",
       "localizations" : {
       "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 %@: %@" : {
     "Received unexpected error in %@ from %@: %@" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "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." : {
     "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" : {
       "localizations" : {
         "bg" : {
         "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 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." : {
     "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" : {
     "Trio State Result" : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "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." : {
     "You can connect Trio to seamlessly upload and manage your diabetes data on Tidepool." : {
       "localizations" : {
       "localizations" : {
         "bg" : {
         "bg" : {

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

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

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

@@ -235,6 +235,15 @@ struct OnboardingStepContent: View {
                                     .resizable()
                                     .resizable()
                                     .scaledToFit()
                                     .scaledToFit()
                                     .frame(width: 60, height: 60)
                                     .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 {
                             } else {
                                 Image(systemName: currentStep.iconName)
                                 Image(systemName: currentStep.iconName)
                                     .font(.system(size: 40))
                                     .font(.system(size: 40))
@@ -300,6 +309,14 @@ struct OnboardingStepContent: View {
                             AlgorithmSubstepView(state: state, substep: currentSMBSubstep)
                             AlgorithmSubstepView(state: state, substep: currentSMBSubstep)
                         case .targetBehavior:
                         case .targetBehavior:
                             AlgorithmSubstepView(state: state, substep: currentTargetBehaviorSubstep)
                             AlgorithmSubstepView(state: state, substep: currentTargetBehaviorSubstep)
+                        case .notifications:
+                            NotificationPermissionStepView()
+                        case .bluetooth:
+                            BluetoothPermissionStepView(
+                                state: state,
+                                bluetoothManager: state.bluetoothManager,
+                                currentStep: $currentStep
+                            )
                         case .completed:
                         case .completed:
                             CompletedStepView()
                             CompletedStepView()
                         }
                         }
@@ -500,6 +517,20 @@ struct OnboardingNavigationButtons: View {
                 currentTargetBehaviorSubstep = .highTempTargetRaisesSensitivity
                 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:
         case .completed:
             state.saveOnboardingData()
             state.saveOnboardingData()
             onboardingManager.completeOnboarding()
             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(
                 completedItemsView(
                     stepIndex: 1,
                     stepIndex: 1,
                     title: String(localized: "Prepare Trio"),
                     title: String(localized: "Prepare Trio"),
-                    steps: [.diagnostics, .nightscout, .unitSelection],
                     description: String(
                     description: String(
                         localized: "App diagnostics sharing, Nightscout setup, and unit and pump model selection are all complete."
                         localized: "App diagnostics sharing, Nightscout setup, and unit and pump model selection are all complete."
                     )
                     )
@@ -34,7 +33,6 @@ struct CompletedStepView: View {
                 completedItemsView(
                 completedItemsView(
                     stepIndex: 2,
                     stepIndex: 2,
                     title: String(localized: "Therapy Settings"),
                     title: String(localized: "Therapy Settings"),
-                    steps: [.glucoseTarget, .basalRates, .carbRatio, .insulinSensitivity],
                     description: String(
                     description: String(
                         localized: "Glucose target, basal rates, carb ratios, and insulin sensitivity match your needs."
                         localized: "Glucose target, basal rates, carb ratios, and insulin sensitivity match your needs."
                     )
                     )
@@ -45,7 +43,6 @@ struct CompletedStepView: View {
                 completedItemsView(
                 completedItemsView(
                     stepIndex: 3,
                     stepIndex: 3,
                     title: String(localized: "Delivery Limits"),
                     title: String(localized: "Delivery Limits"),
-                    steps: [.deliveryLimits],
                     description: String(
                     description: String(
                         localized: "Safety boundaries for insulin delivery and carb entries are set to help Trio keep you safe."
                         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(
                 completedItemsView(
                     stepIndex: 4,
                     stepIndex: 4,
                     title: String(localized: "Algorithm Settings"),
                     title: String(localized: "Algorithm Settings"),
-                    steps: [.autosensSettings, .smbSettings, .targetBehavior],
                     description: String(localized: "Trio’s algorithm features are customized to fit your preferences and needs.")
                     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()
             .padding()
             .background(Color.green.opacity(0.1))
             .background(Color.green.opacity(0.1))
@@ -77,7 +81,6 @@ struct CompletedStepView: View {
     @ViewBuilder private func completedItemsView(
     @ViewBuilder private func completedItemsView(
         stepIndex: Int,
         stepIndex: Int,
         title: String,
         title: String,
-        steps _: [OnboardingStep],
         description: String
         description: String
     ) -> some View {
     ) -> some View {
         VStack(alignment: .leading, spacing: 10) {
         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."
                         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()
             .padding()
             .background(Color.chart.opacity(0.65))
             .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 autosensSettings
     case smbSettings
     case smbSettings
     case targetBehavior
     case targetBehavior
+    case notifications
+    case bluetooth
     case completed
     case completed
 
 
     var id: Int { rawValue }
     var id: Int { rawValue }
@@ -69,6 +71,10 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return String(localized: "Super Micro Bolus")
             return String(localized: "Super Micro Bolus")
         case .targetBehavior:
         case .targetBehavior:
             return String(localized: "Target Behavior")
             return String(localized: "Target Behavior")
+        case .notifications:
+            return String(localized: "Notifications")
+        case .bluetooth:
+            return String(localized: "Bluetooth")
         case .completed:
         case .completed:
             return String(localized: "All Set!")
             return String(localized: "All Set!")
         }
         }
@@ -137,6 +143,10 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
             return String(
             return String(
                 localized: "Target Behavior allows you to adjust how temporary targets influence ISF, basal, and auto-targeting based on sensitivity or resistance."
                 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:
         case .completed:
             return String(
             return String(
                 localized: "Great job! You've completed the initial setup of Trio. You can always adjust these settings later in the app."
                 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"
             return "bolt.fill"
         case .targetBehavior:
         case .targetBehavior:
             return "gyroscope"
             return "gyroscope"
+        case .notifications:
+            return "bell.badge.fill"
+        case .bluetooth:
+            return "logo.bluetooth.capsule.portrait.fill"
         case .completed:
         case .completed:
             return "checkmark.circle.fill"
             return "checkmark.circle.fill"
         }
         }
@@ -203,10 +217,12 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
         switch self {
         switch self {
         case .algorithmSettings,
         case .algorithmSettings,
              .autosensSettings,
              .autosensSettings,
+             .bluetooth,
              .completed,
              .completed,
              .deliveryLimits,
              .deliveryLimits,
              .diagnostics,
              .diagnostics,
              .nightscout,
              .nightscout,
+             .notifications,
              .overview,
              .overview,
              .smbSettings,
              .smbSettings,
              .startupGuide,
              .startupGuide,

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

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