From b4b22e0e05bdfec7ea02d8e7f195d5595b32067b Mon Sep 17 00:00:00 2001 From: jack <212554440+jackjackbits@users.noreply.github.com> Date: Fri, 1 Aug 2025 21:51:32 +0200 Subject: [PATCH] Add Bluetooth status alerts (#385) - Add alert UI to notify users when Bluetooth is off/unauthorized - Monitor Bluetooth state changes in BluetoothMeshService - Show appropriate messages for different Bluetooth states - Add Settings button to open system settings on iOS - Check initial Bluetooth state on app startup Co-authored-by: jack --- bitchat/Services/BluetoothMeshService.swift | 23 +++++++++++++ bitchat/ViewModels/ChatViewModel.swift | 36 +++++++++++++++++++++ bitchat/Views/ContentView.swift | 15 +++++++++ 3 files changed, 74 insertions(+) diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift index 38e99923..f3d22c76 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -1937,6 +1937,15 @@ class BluetoothMeshService: NSObject { cleanupStaleSessions() cleanupUnknownPeers() + // Report initial Bluetooth state to delegate + if let chatViewModel = delegate as? ChatViewModel { + // Use the central manager state as primary indicator + let currentState = centralManager?.state ?? .unknown + Task { @MainActor in + chatViewModel.updateBluetoothState(currentState) + } + } + // Starting services // Start both central and peripheral services if centralManager?.state == .poweredOn { @@ -4988,6 +4997,13 @@ extension BluetoothMeshService: CBCentralManagerDelegate { @unknown default: break } + // Notify ChatViewModel of Bluetooth state change + if let chatViewModel = delegate as? ChatViewModel { + Task { @MainActor in + chatViewModel.updateBluetoothState(central.state) + } + } + if central.state == .unsupported { } else if central.state == .unauthorized { } else if central.state == .poweredOff { @@ -5540,6 +5556,13 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate { @unknown default: break } + // Notify ChatViewModel of Bluetooth state change + if let chatViewModel = delegate as? ChatViewModel { + Task { @MainActor in + chatViewModel.updateBluetoothState(peripheral.state) + } + } + switch peripheral.state { case .unsupported: break diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 7c28ea18..069e9872 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -82,6 +82,7 @@ import SwiftUI import Combine import CryptoKit import CommonCrypto +import CoreBluetooth #if os(iOS) import UIKit #endif @@ -155,6 +156,11 @@ class ChatViewModel: ObservableObject, BitchatDelegate { @Published var verifiedFingerprints: Set = [] // Set of verified fingerprints @Published var showingFingerprintFor: String? = nil // Currently showing fingerprint sheet for peer + // Bluetooth state management + @Published var showBluetoothAlert = false + @Published var bluetoothAlertMessage = "" + @Published var bluetoothState: CBManagerState = .unknown + // Messages are naturally ephemeral - no persistent storage // MARK: - Message Delivery Tracking @@ -819,6 +825,36 @@ class ChatViewModel: ObservableObject, BitchatDelegate { } } + // MARK: - Bluetooth State Management + + /// Updates the Bluetooth state and shows appropriate alerts + /// - Parameter state: The current Bluetooth manager state + @MainActor + func updateBluetoothState(_ state: CBManagerState) { + bluetoothState = state + + switch state { + case .poweredOff: + bluetoothAlertMessage = "Bluetooth is turned off. Please turn on Bluetooth in Settings to use BitChat." + showBluetoothAlert = true + case .unauthorized: + bluetoothAlertMessage = "BitChat needs Bluetooth permission to connect with nearby devices. Please enable Bluetooth access in Settings." + showBluetoothAlert = true + case .unsupported: + bluetoothAlertMessage = "This device does not support Bluetooth. BitChat requires Bluetooth to function." + showBluetoothAlert = true + case .poweredOn: + // Hide alert when Bluetooth is powered on + showBluetoothAlert = false + bluetoothAlertMessage = "" + case .unknown, .resetting: + // Don't show alerts for transient states + showBluetoothAlert = false + @unknown default: + showBluetoothAlert = false + } + } + // MARK: - Private Chat Management /// Initiates a private chat session with a peer. diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 5943191e..28bc0cd8 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -7,6 +7,9 @@ // import SwiftUI +#if os(iOS) +import UIKit +#endif // MARK: - Supporting Types @@ -227,6 +230,18 @@ struct ContentView: View { Button("cancel", role: .cancel) {} } + .alert("Bluetooth Required", isPresented: $viewModel.showBluetoothAlert) { + Button("Settings") { + #if os(iOS) + if let url = URL(string: UIApplication.openSettingsURLString) { + UIApplication.shared.open(url) + } + #endif + } + Button("OK", role: .cancel) {} + } message: { + Text(viewModel.bluetoothAlertMessage) + } .onDisappear { // Clean up timers scrollThrottleTimer?.invalidate()