diff --git a/bitchat/Protocols/BitchatProtocol.swift b/bitchat/Protocols/BitchatProtocol.swift index 0c27d3d5..7b21136a 100644 --- a/bitchat/Protocols/BitchatProtocol.swift +++ b/bitchat/Protocols/BitchatProtocol.swift @@ -59,6 +59,7 @@ /// import Foundation +import CoreBluetooth // MARK: - Message Types @@ -164,14 +165,17 @@ protocol BitchatDelegate: AnyObject { func didConnectToPeer(_ peerID: String) func didDisconnectFromPeer(_ peerID: String) func didUpdatePeerList(_ peers: [String]) - + // Optional method to check if a fingerprint belongs to a favorite peer func isFavorite(fingerprint: String) -> Bool - + func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) // Low-level events for better separation of concerns func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) + + // Bluetooth state updates for user notifications + func didUpdateBluetoothState(_ state: CBManagerState) func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) } diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index 259cdf3b..bd612398 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -552,7 +552,11 @@ final class BLEService: NSObject { func getNoiseService() -> NoiseEncryptionService { return noiseService } - + + func getCurrentBluetoothState() -> CBManagerState { + return centralManager?.state ?? .unknown + } + // MARK: Messaging // Transport protocol conformance helper: simplified public message send @@ -694,6 +698,11 @@ extension BLEService: GossipSyncManager.Delegate { extension BLEService: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { + // Notify delegate about state change on main thread + Task { @MainActor in + self.delegate?.didUpdateBluetoothState(central.state) + } + if central.state == .poweredOn { // Start scanning - use allow duplicates for faster discovery when active startScanning() diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 8955181f..3fe260ea 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -549,7 +549,16 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Start mesh service immediately meshService.startServices() - + + // Check initial Bluetooth state after a brief delay to allow centralManager initialization + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + guard let self = self else { return } + if let bleService = self.meshService as? BLEService { + let state = bleService.getCurrentBluetoothState() + self.updateBluetoothState(state) + } + } + // Announce Tor status (geohash-only; do not show in mesh chat). Only when auto-start is allowed. if TorManager.shared.torEnforced && !torStatusAnnounced && TorManager.shared.isAutoStartAllowed() { torStatusAnnounced = true @@ -2834,6 +2843,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { @MainActor @objc private func appDidBecomeActive() { + // Check Bluetooth state and show alert if needed + if let bleService = meshService as? BLEService { + let currentState = bleService.getCurrentBluetoothState() + updateBluetoothState(currentState) + } + // When app becomes active, send read receipts for visible private chat if let peerID = selectedPrivateChatPeer { // Try immediately @@ -4597,8 +4612,16 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // Mention parsing moved from BLE – use the existing non-optional helper below + // MARK: - Bluetooth State Monitoring + + func didUpdateBluetoothState(_ state: CBManagerState) { + Task { @MainActor in + updateBluetoothState(state) + } + } + // MARK: - Peer Connection Events - + func didConnectToPeer(_ peerID: String) { SecureLogger.debug("🤝 Peer connected: \(peerID)", category: .session) diff --git a/bitchatTests/BLEServiceTests.swift b/bitchatTests/BLEServiceTests.swift index ee2a58a8..c691ca17 100644 --- a/bitchatTests/BLEServiceTests.swift +++ b/bitchatTests/BLEServiceTests.swift @@ -286,4 +286,7 @@ private final class MockBitchatDelegate: BitchatDelegate { func didUpdatePeerList(_ peers: [String]) {} func isFavorite(fingerprint: String) -> Bool { return false } func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {} + func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) {} + func didUpdateBluetoothState(_ state: CBManagerState) {} + func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) {} } diff --git a/bitchatTests/Fragmentation/FragmentationTests.swift b/bitchatTests/Fragmentation/FragmentationTests.swift index b3b41e7a..c2ddfde1 100644 --- a/bitchatTests/Fragmentation/FragmentationTests.swift +++ b/bitchatTests/Fragmentation/FragmentationTests.swift @@ -8,6 +8,7 @@ import Testing import Foundation +import CoreBluetooth @testable import bitchat struct FragmentationTests { @@ -134,6 +135,7 @@ extension FragmentationTests { func isFavorite(fingerprint: String) -> Bool { false } func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {} func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) {} + func didUpdateBluetoothState(_ state: CBManagerState) {} func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) { publicMessages.append((peerID, nickname, content)) }