| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- //
- // 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)
- }
- }
|