Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
18 changes: 18 additions & 0 deletions __mocks__/expo-audio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Mock for expo-audio to understand the PermissionStatus structure
export const getRecordingPermissionsAsync = jest.fn();
export const requestRecordingPermissionsAsync = jest.fn();

// Default mock implementation
getRecordingPermissionsAsync.mockResolvedValue({
granted: false,
canAskAgain: true,
expires: 'never',
status: 'undetermined',
});

requestRecordingPermissionsAsync.mockResolvedValue({
granted: true,
canAskAgain: true,
expires: 'never',
status: 'granted',
});
88 changes: 88 additions & 0 deletions __mocks__/react-native-ble-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Mock for react-native-ble-manager
export type BleState = 'on' | 'off' | 'turning_on' | 'turning_off' | 'unsupported' | 'unknown';

export interface Peripheral {
id: string;
name?: string;
rssi?: number;
advertising?: {
isConnectable?: boolean;
localName?: string;
manufacturerData?: any;
serviceUUIDs?: string[];
txPowerLevel?: number;
};
}

export interface BleManagerDidUpdateValueForCharacteristicEvent {
peripheral: string;
characteristic: string;
service: string;
value: number[];
}

const mockPeripherals: Peripheral[] = [];
let mockState: BleState = 'on';
let mockIsScanning = false;

const BleManager = {
start: jest.fn().mockResolvedValue(undefined),

checkState: jest.fn().mockImplementation(() => Promise.resolve(mockState)),

scan: jest.fn().mockImplementation((serviceUUIDs: string[], duration: number, allowDuplicates: boolean = false) => {
mockIsScanning = true;
// Simulate scanning timeout
setTimeout(() => {
mockIsScanning = false;
}, duration * 1000);
return Promise.resolve();
}),

stopScan: jest.fn().mockImplementation(() => {
mockIsScanning = false;
return Promise.resolve();
}),

connect: jest.fn().mockResolvedValue(undefined),

disconnect: jest.fn().mockResolvedValue(undefined),

retrieveServices: jest.fn().mockResolvedValue(undefined),

startNotification: jest.fn().mockResolvedValue(undefined),

stopNotification: jest.fn().mockResolvedValue(undefined),

getConnectedPeripherals: jest.fn().mockResolvedValue([]),

getDiscoveredPeripherals: jest.fn().mockResolvedValue(mockPeripherals),

isPeripheralConnected: jest.fn().mockResolvedValue(false),

// Mock utilities for testing
setMockState: (state: BleState) => {
mockState = state;
},

addMockPeripheral: (peripheral: Peripheral) => {
mockPeripherals.push(peripheral);
},

clearMockPeripherals: () => {
mockPeripherals.length = 0;
},

getMockPeripherals: () => [...mockPeripherals],

isMockScanning: () => mockIsScanning,
};

// Set up as any for easier mocking
(BleManager as any).setMockState = BleManager.setMockState;
(BleManager as any).addMockPeripheral = BleManager.addMockPeripheral;
(BleManager as any).clearMockPeripherals = BleManager.clearMockPeripherals;
(BleManager as any).getMockPeripherals = BleManager.getMockPeripherals;
(BleManager as any).isMockScanning = BleManager.isMockScanning;

export default BleManager;
19 changes: 9 additions & 10 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
bundleIdentifier: Env.BUNDLE_ID,
requireFullScreen: true,
infoPlist: {
UIBackgroundModes: ['remote-notification', 'audio'],
UIBackgroundModes: ['remote-notification', 'audio', 'bluetooth-central'],
ITSAppUsesNonExemptEncryption: false,
NSBluetoothAlwaysUsageDescription: 'Allow Resgrid Unit to connect to bluetooth devices for PTT.',
},
},
experiments: {
Expand Down Expand Up @@ -234,14 +235,6 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
url: 'https://sentry.resgrid.net/',
},
],
[
'react-native-ble-plx',
{
isBackgroundEnabled: true,
modes: ['peripheral', 'central'],
bluetoothAlwaysPermission: 'Allow Resgrid Unit to connect to bluetooth devices for PTT.',
},
],
[
'expo-navigation-bar',
{
Expand All @@ -250,7 +243,13 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
behavior: 'inset-touch',
},
],
'expo-audio',
[
'expo-audio',
{
microphonePermission: 'Allow Resgrid Unit to access the microphone for audio input used in PTT and calls.',
},
],
'react-native-ble-manager',
'@livekit/react-native-expo-plugin',
'@config-plugins/react-native-webrtc',
'./customGradle.plugin.js',
Expand Down
72 changes: 72 additions & 0 deletions docs/bluetooth-device-selection-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Bluetooth Device Selection Dialog Migration to react-native-ble-manager

## Overview

The Bluetooth device selection dialog has been updated to support the migration from `react-native-ble-plx` to `react-native-ble-manager` in the bluetooth audio service.

## Key Changes Made

### 1. Enhanced Error Handling
- **Scan Error Recovery**: Added reset of `hasScanned` state on scan errors to allow retry
- **Connection Error Display**: Added display of connection errors from the bluetooth store
- **Bluetooth State Handling**: Improved bluetooth state warnings with specific messages for different states

### 2. Auto-Connect Functionality
- **Smart Device Selection**: When a device is selected as preferred, the dialog now attempts to automatically connect to it if not already connected
- **Non-blocking Connection**: Connection failures during selection don't block the preference setting - they're logged as warnings

### 3. Enhanced Device Information Display
- **Audio Capability Badge**: Added display of `hasAudioCapability` property for devices
- **Connection Status**: Enhanced display of connection status in the selected device section
- **Better Device Metadata**: Improved display of device capabilities and connection state

### 4. Improved Scanning Management
- **Automatic Scan Cleanup**: Added cleanup to stop scanning when dialog closes or component unmounts
- **Scan State Management**: Better handling of scan state to prevent memory leaks

### 5. Updated Dependencies and Imports
- **Store Compatibility**: Updated to use `connectionError` from the bluetooth store
- **Type Safety**: Maintained compatibility with the new `BluetoothAudioDevice` interface from react-native-ble-manager

### 6. Testing Infrastructure
- **Mock Creation**: Created new mock for `react-native-ble-manager` to replace old `react-native-ble-plx` mock
- **Icon Mocking**: Added proper mocking for lucide icons to avoid SVG-related test failures
- **Comprehensive Tests**: Added tests for new functionality including error states and auto-connect behavior

## Compatibility Notes

### What Stayed the Same
- **Component Interface**: The component props and public API remain unchanged
- **UI/UX**: The visual design and user interaction patterns are maintained
- **Core Functionality**: Device selection and preference setting work exactly as before

### What Changed Internally
- **Service Integration**: Now properly integrates with the migrated bluetooth service using react-native-ble-manager
- **Error Handling**: More robust error handling and user feedback
- **State Management**: Better state cleanup and scanning lifecycle management

## Migration Benefits

1. **Better Reliability**: Improved error handling and state management
2. **Enhanced UX**: Auto-connect functionality and better status display
3. **Improved Performance**: Better scan lifecycle management prevents memory leaks
4. **Future-Proof**: Compatible with the new react-native-ble-manager architecture

## Technical Details

### New Properties Used
- `hasAudioCapability`: Displayed as a badge for audio-capable devices
- `connectionError`: Shown in error display section
- Enhanced bluetooth state handling with specific error messages

### Enhanced Functionality
- **Auto-connect on selection**: Attempts to connect when device is selected as preferred
- **Scan cleanup**: Properly stops scanning when dialog closes
- **Error recovery**: Better error handling with retry capabilities

## Testing
- Created comprehensive test suite covering all new functionality
- Added mocks for react-native-ble-manager compatibility
- Tests cover error states, auto-connect behavior, and scan management

The updated dialog is now fully compatible with the react-native-ble-manager migration while providing enhanced functionality and better user experience.
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@
"expo-linking": "~7.0.3",
"expo-localization": "~16.0.0",
"expo-location": "~18.0.10",
"expo-modules-core": "~2.2.3",
"expo-navigation-bar": "~4.0.9",
"expo-notifications": "~0.29.14",
"expo-router": "~4.0.21",
Expand All @@ -137,20 +136,19 @@
"moti": "~0.29.0",
"nativewind": "~4.1.21",
"react": "18.3.1",
"react-dom": "^19.1.0",
"react-dom": "18.3.1",
"react-error-boundary": "~4.0.13",
"react-hook-form": "~7.53.0",
"react-i18next": "~15.0.1",
"react-native": "0.76.9",
"react-native-base64": "~0.2.1",
"react-native-ble-plx": "^3.5.0",
"react-native-ble-manager": "^12.1.5",
"react-native-edge-to-edge": "~1.1.2",
"react-native-flash-message": "~0.4.2",
"react-native-gesture-handler": "~2.20.2",
"react-native-keyboard-controller": "~1.15.2",
"react-native-logs": "~5.3.0",
"react-native-mmkv": "~3.1.0",
"react-native-permissions": "^5.4.1",
"react-native-reanimated": "~3.16.1",
"react-native-restart": "0.0.27",
"react-native-safe-area-context": "4.12.0",
Expand Down
Loading
Loading