mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 22:05:21 +00:00
Show Bluetooth permission alerts on launch and foreground (#765)
* Fix test suite peer ID collisions Use unique peer IDs for each test suite to prevent global registry collisions when Swift Testing runs suites in parallel. - PrivateChatE2ETests: PRIV_* prefix - PublicChatE2ETests: PUB_* prefix - Update all peer ID references to use actual instance IDs This fixes the race condition where simplePublicMessage() was receiving duplicate deliveries due to registry contamination. * Add Bluetooth permission & state alerts on launch and foreground Wire up existing Bluetooth alert infrastructure to show notifications when Bluetooth is off, unauthorized, or unsupported. Changes: - Add didUpdateBluetoothState() to BitchatDelegate protocol - BLEService now notifies delegate when Bluetooth state changes - ChatViewModel implements delegate method to show alerts - Check Bluetooth state on app launch (after 100ms delay) - Check Bluetooth state when app comes to foreground - Add getCurrentBluetoothState() method to BLEService The UI alert already existed but wasn't wired up. Now users will see appropriate alerts for: - Bluetooth turned off - Bluetooth permission denied - Bluetooth unsupported on device Alert includes a button to open Settings on iOS. --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -59,6 +59,7 @@
|
|||||||
///
|
///
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
// MARK: - Message Types
|
// MARK: - Message Types
|
||||||
|
|
||||||
@@ -164,14 +165,17 @@ protocol BitchatDelegate: AnyObject {
|
|||||||
func didConnectToPeer(_ peerID: String)
|
func didConnectToPeer(_ peerID: String)
|
||||||
func didDisconnectFromPeer(_ peerID: String)
|
func didDisconnectFromPeer(_ peerID: String)
|
||||||
func didUpdatePeerList(_ peers: [String])
|
func didUpdatePeerList(_ peers: [String])
|
||||||
|
|
||||||
// Optional method to check if a fingerprint belongs to a favorite peer
|
// Optional method to check if a fingerprint belongs to a favorite peer
|
||||||
func isFavorite(fingerprint: String) -> Bool
|
func isFavorite(fingerprint: String) -> Bool
|
||||||
|
|
||||||
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus)
|
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus)
|
||||||
|
|
||||||
// Low-level events for better separation of concerns
|
// Low-level events for better separation of concerns
|
||||||
func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date)
|
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)
|
func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -552,7 +552,11 @@ final class BLEService: NSObject {
|
|||||||
func getNoiseService() -> NoiseEncryptionService {
|
func getNoiseService() -> NoiseEncryptionService {
|
||||||
return noiseService
|
return noiseService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCurrentBluetoothState() -> CBManagerState {
|
||||||
|
return centralManager?.state ?? .unknown
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Messaging
|
// MARK: Messaging
|
||||||
|
|
||||||
// Transport protocol conformance helper: simplified public message send
|
// Transport protocol conformance helper: simplified public message send
|
||||||
@@ -694,6 +698,11 @@ extension BLEService: GossipSyncManager.Delegate {
|
|||||||
|
|
||||||
extension BLEService: CBCentralManagerDelegate {
|
extension BLEService: CBCentralManagerDelegate {
|
||||||
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||||
|
// Notify delegate about state change on main thread
|
||||||
|
Task { @MainActor in
|
||||||
|
self.delegate?.didUpdateBluetoothState(central.state)
|
||||||
|
}
|
||||||
|
|
||||||
if central.state == .poweredOn {
|
if central.state == .poweredOn {
|
||||||
// Start scanning - use allow duplicates for faster discovery when active
|
// Start scanning - use allow duplicates for faster discovery when active
|
||||||
startScanning()
|
startScanning()
|
||||||
|
|||||||
@@ -549,7 +549,16 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
// Start mesh service immediately
|
// Start mesh service immediately
|
||||||
meshService.startServices()
|
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.
|
// 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() {
|
if TorManager.shared.torEnforced && !torStatusAnnounced && TorManager.shared.isAutoStartAllowed() {
|
||||||
torStatusAnnounced = true
|
torStatusAnnounced = true
|
||||||
@@ -2834,6 +2843,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@objc private func appDidBecomeActive() {
|
@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
|
// When app becomes active, send read receipts for visible private chat
|
||||||
if let peerID = selectedPrivateChatPeer {
|
if let peerID = selectedPrivateChatPeer {
|
||||||
// Try immediately
|
// Try immediately
|
||||||
@@ -4597,8 +4612,16 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mention parsing moved from BLE – use the existing non-optional helper below
|
// 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
|
// MARK: - Peer Connection Events
|
||||||
|
|
||||||
func didConnectToPeer(_ peerID: String) {
|
func didConnectToPeer(_ peerID: String) {
|
||||||
SecureLogger.debug("🤝 Peer connected: \(peerID)", category: .session)
|
SecureLogger.debug("🤝 Peer connected: \(peerID)", category: .session)
|
||||||
|
|
||||||
|
|||||||
@@ -286,4 +286,7 @@ private final class MockBitchatDelegate: BitchatDelegate {
|
|||||||
func didUpdatePeerList(_ peers: [String]) {}
|
func didUpdatePeerList(_ peers: [String]) {}
|
||||||
func isFavorite(fingerprint: String) -> Bool { return false }
|
func isFavorite(fingerprint: String) -> Bool { return false }
|
||||||
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {}
|
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) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Testing
|
import Testing
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
@testable import bitchat
|
@testable import bitchat
|
||||||
|
|
||||||
struct FragmentationTests {
|
struct FragmentationTests {
|
||||||
@@ -134,6 +135,7 @@ extension FragmentationTests {
|
|||||||
func isFavorite(fingerprint: String) -> Bool { false }
|
func isFavorite(fingerprint: String) -> Bool { false }
|
||||||
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {}
|
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {}
|
||||||
func didReceiveNoisePayload(from peerID: String, type: NoisePayloadType, payload: Data, timestamp: Date) {}
|
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) {
|
func didReceivePublicMessage(from peerID: String, nickname: String, content: String, timestamp: Date) {
|
||||||
publicMessages.append((peerID, nickname, content))
|
publicMessages.append((peerID, nickname, content))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user