// BLE Context - Global state for Bluetooth management import React, { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react'; import { bleManager, WPDevice, WiFiNetwork, WiFiStatus, isBLEAvailable, checkBLEReadiness } from '@/services/ble'; import { setOnLogoutBLECleanupCallback } from '@/services/api'; import { BleManager } from 'react-native-ble-plx'; interface BLEContextType { // State foundDevices: WPDevice[]; isScanning: boolean; connectedDevices: Set; isBLEAvailable: boolean; error: string | null; permissionError: boolean; // true if error is related to permissions // Actions scanDevices: () => Promise; stopScan: () => void; connectDevice: (deviceId: string) => Promise; disconnectDevice: (deviceId: string) => Promise; getWiFiList: (deviceId: string) => Promise; setWiFi: (deviceId: string, ssid: string, password: string) => Promise; getCurrentWiFi: (deviceId: string) => Promise; rebootDevice: (deviceId: string) => Promise; cleanupBLE: () => Promise; clearError: () => void; checkPermissions: () => Promise; // Manual permission check with UI prompts } const BLEContext = createContext(undefined); export function BLEProvider({ children }: { children: ReactNode }) { const [foundDevices, setFoundDevices] = useState([]); const [isScanning, setIsScanning] = useState(false); const [connectedDevices, setConnectedDevices] = useState>(new Set()); const [error, setError] = useState(null); const [permissionError, setPermissionError] = useState(false); // Lazy BleManager instance for permission checks const [bleManagerInstance] = useState(() => new BleManager()); const isPermissionError = (errorMessage: string): boolean => { const permissionKeywords = [ 'permission', 'unauthorized', 'not granted', 'denied', 'bluetooth is disabled', 'enable it in settings', ]; const lowerMessage = errorMessage.toLowerCase(); return permissionKeywords.some(keyword => lowerMessage.includes(keyword)); }; const checkPermissions = useCallback(async (): Promise => { try { setError(null); setPermissionError(false); const ready = await checkBLEReadiness(bleManagerInstance); return ready; } catch { const errorMsg = 'Failed to check Bluetooth permissions'; setError(errorMsg); setPermissionError(true); return false; } }, [bleManagerInstance]); const scanDevices = useCallback(async () => { try { setError(null); setPermissionError(false); setIsScanning(true); const devices = await bleManager.scanDevices(); // Sort by RSSI (strongest first) const sorted = devices.sort((a, b) => b.rssi - a.rssi); setFoundDevices(sorted); } catch (err: any) { const errorMsg = err.message || 'Failed to scan for devices'; setError(errorMsg); setPermissionError(isPermissionError(errorMsg)); throw err; } finally { setIsScanning(false); } }, []); const stopScan = useCallback(() => { bleManager.stopScan(); setIsScanning(false); }, []); const connectDevice = useCallback(async (deviceId: string): Promise => { try { setError(null); setPermissionError(false); const success = await bleManager.connectDevice(deviceId); if (success) { setConnectedDevices(prev => new Set(prev).add(deviceId)); } else { // Connection failed but no exception - set user-friendly error const errorMsg = 'Could not connect to sensor. Please move closer and try again.'; setError(errorMsg); setPermissionError(false); } return success; } catch (err: any) { const errorMsg = err.message || 'Failed to connect to device'; setError(errorMsg); setPermissionError(isPermissionError(errorMsg)); return false; } }, []); const disconnectDevice = useCallback(async (deviceId: string): Promise => { try { await bleManager.disconnectDevice(deviceId); setConnectedDevices(prev => { const next = new Set(prev); next.delete(deviceId); return next; }); } catch (err: any) { setError(err.message || 'Failed to disconnect device'); } }, []); const getWiFiList = useCallback(async (deviceId: string): Promise => { try { setError(null); const networks = await bleManager.getWiFiList(deviceId); return networks; } catch (err: any) { setError(err.message || 'Failed to get WiFi networks'); throw err; } }, []); const setWiFi = useCallback( async (deviceId: string, ssid: string, password: string): Promise => { try { setError(null); return await bleManager.setWiFi(deviceId, ssid, password); } catch (err: any) { setError(err.message || 'Failed to configure WiFi'); throw err; } }, [] ); const getCurrentWiFi = useCallback( async (deviceId: string): Promise => { try { setError(null); return await bleManager.getCurrentWiFi(deviceId); } catch (err: any) { setError(err.message || 'Failed to get current WiFi'); throw err; } }, [] ); const rebootDevice = useCallback(async (deviceId: string): Promise => { try { setError(null); await bleManager.rebootDevice(deviceId); // Remove from connected devices setConnectedDevices(prev => { const next = new Set(prev); next.delete(deviceId); return next; }); } catch (err: any) { setError(err.message || 'Failed to reboot device'); throw err; } }, []); const clearError = useCallback(() => { setError(null); setPermissionError(false); }, []); const cleanupBLE = useCallback(async () => { try { // Stop any ongoing scan if (isScanning) { stopScan(); } // Cleanup via bleManager (disconnects all devices) await bleManager.cleanup(); // Clear context state setFoundDevices([]); setConnectedDevices(new Set()); setError(null); } catch { // Don't throw - we want to allow logout to proceed even if BLE cleanup fails } }, [isScanning, stopScan]); // Register BLE cleanup callback for logout useEffect(() => { setOnLogoutBLECleanupCallback(cleanupBLE); return () => { setOnLogoutBLECleanupCallback(null); }; }, [cleanupBLE]); const value: BLEContextType = { foundDevices, isScanning, connectedDevices, isBLEAvailable, error, permissionError, scanDevices, stopScan, connectDevice, disconnectDevice, getWiFiList, setWiFi, getCurrentWiFi, rebootDevice, cleanupBLE, clearError, checkPermissions, }; return {children}; } export function useBLE() { const context = useContext(BLEContext); if (context === undefined) { throw new Error('useBLE must be used within a BLEProvider'); } return context; }