Skip to content

Commit c99bb09

Browse files
committed
RU-T42 Working on fix PTT issues.
1 parent 2c24653 commit c99bb09

21 files changed

Lines changed: 1018 additions & 330 deletions

app.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
8080
'android.permission.FOREGROUND_SERVICE_MICROPHONE',
8181
'android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE',
8282
'android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK',
83+
'android.permission.READ_PHONE_STATE',
84+
'android.permission.READ_PHONE_NUMBERS',
85+
'android.permission.MANAGE_OWN_CALLS',
8386
],
8487
},
8588
web: {

expo-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
/// <reference types="expo/types" />
22

3-
// NOTE: This file should not be edited and should be in your git ignore
3+
// NOTE: This file should not be edited and should be in your git ignore

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
"axios": "~1.12.0",
111111
"babel-plugin-module-resolver": "^5.0.2",
112112
"buffer": "^6.0.3",
113-
"countly-sdk-react-native-bridge": "^25.4.0",
113+
"countly-sdk-react-native-bridge": "25.4.1",
114114
"date-fns": "^4.1.0",
115115
"expo": "~53.0.23",
116116
"expo-application": "~6.1.5",
@@ -148,6 +148,7 @@
148148
"mapbox-gl": "3.18.1",
149149
"moti": "~0.29.0",
150150
"nativewind": "~4.1.21",
151+
"promise": "8.3.0",
151152
"react": "19.0.0",
152153
"react-dom": "19.0.0",
153154
"react-error-boundary": "~4.0.13",
@@ -167,6 +168,7 @@
167168
"react-native-restart": "0.0.27",
168169
"react-native-safe-area-context": "5.4.0",
169170
"react-native-screens": "~4.11.1",
171+
"react-native-sound": "^0.13.0",
170172
"react-native-svg": "15.11.2",
171173
"react-native-web": "^0.20.0",
172174
"react-native-webview": "~13.13.1",
@@ -258,6 +260,7 @@
258260
"initVersion": "7.0.4"
259261
},
260262
"resolutions": {
261-
"form-data": "4.0.4"
263+
"form-data": "4.0.4",
264+
"promise": "8.3.0"
262265
}
263266
}

src/components/livekit/__tests__/livekit-bottom-sheet.test.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ jest.mock('i18next', () => ({
8585
},
8686
}));
8787

88+
// Mock Actionsheet
89+
jest.mock('../../ui/actionsheet', () => {
90+
const React = require('react');
91+
const { View } = require('react-native');
92+
return {
93+
Actionsheet: ({ children, isOpen }: any) => isOpen ? <View testID="mock-actionsheet">{children}</View> : null,
94+
ActionsheetBackdrop: () => null,
95+
ActionsheetContent: ({ children }: any) => <View testID="mock-actionsheet-content">{children}</View>,
96+
ActionsheetDragIndicatorWrapper: ({ children }: any) => <View>{children}</View>,
97+
ActionsheetDragIndicator: () => null,
98+
};
99+
});
100+
88101
// Import after mocks to avoid the React Native CSS Interop issue
89102
import { useBluetoothAudioStore } from '@/stores/app/bluetooth-audio-store';
90103
import { useLiveKitStore } from '@/stores/app/livekit-store';
@@ -451,6 +464,63 @@ describe('LiveKitBottomSheet', () => {
451464
await audioService.playDisconnectionSound();
452465
expect(audioService.playDisconnectionSound).toHaveBeenCalledTimes(1);
453466
});
467+
468+
it('should return to room select when back is pressed from audio settings if entered from room select', () => {
469+
const { fireEvent } = require('@testing-library/react-native');
470+
471+
mockUseLiveKitStore.mockReturnValue({
472+
...defaultLiveKitState,
473+
isBottomSheetVisible: true,
474+
availableRooms: mockAvailableRooms,
475+
});
476+
477+
const component = render(<LiveKitBottomSheet />);
478+
479+
// Navigate to audio settings
480+
const settingsButton = component.getByTestId('header-audio-settings-button');
481+
fireEvent.press(settingsButton);
482+
483+
// Verify we are in audio settings
484+
expect(component.getByTestId('audio-settings-view')).toBeTruthy();
485+
486+
// Press back
487+
const backButton = component.getByTestId('back-button');
488+
fireEvent.press(backButton);
489+
490+
// Verify we are back in room select (by checking for join buttons)
491+
expect(component.getByTestId('room-list')).toBeTruthy();
492+
});
493+
494+
it('should return to connected view when back is pressed from audio settings if entered from connected view', async () => {
495+
const { fireEvent } = require('@testing-library/react-native');
496+
497+
mockUseLiveKitStore.mockReturnValue({
498+
...defaultLiveKitState,
499+
isBottomSheetVisible: true,
500+
isConnected: true,
501+
currentRoomInfo: mockCurrentRoomInfo,
502+
currentRoom: mockRoom,
503+
});
504+
505+
const component = render(<LiveKitBottomSheet />);
506+
507+
// Verify we are in connected view
508+
await expect(component.findByTestId('connected-view')).resolves.toBeTruthy();
509+
510+
// Navigate to audio settings
511+
const settingsButton = component.getByTestId('audio-settings-button');
512+
fireEvent.press(settingsButton);
513+
514+
// Verify we are in audio settings
515+
expect(component.getByTestId('audio-settings-view')).toBeTruthy();
516+
517+
// Press back
518+
const backButton = component.getByTestId('back-button');
519+
fireEvent.press(backButton);
520+
521+
// Verify we are back in connected view
522+
expect(component.getByTestId('connected-view')).toBeTruthy();
523+
});
454524
});
455525

456526
describe('Analytics', () => {

src/components/livekit/livekit-bottom-sheet.tsx

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { t } from 'i18next';
2-
import { Audio, InterruptionModeIOS } from 'expo-av';
32
import { Headphones, Mic, MicOff, PhoneOff, Settings } from 'lucide-react-native';
43
import { useColorScheme } from 'nativewind';
54
import React, { useCallback, useEffect, useRef, useState } from 'react';
@@ -12,7 +11,7 @@ import { useBluetoothAudioStore } from '@/stores/app/bluetooth-audio-store';
1211

1312
import { Card } from '../../components/ui/card';
1413
import { Text } from '../../components/ui/text';
15-
import { useLiveKitStore } from '../../stores/app/livekit-store';
14+
import { applyAudioRouting, useLiveKitStore } from '../../stores/app/livekit-store';
1615
import { AudioDeviceSelection } from '../settings/audio-device-selection';
1716
import { Actionsheet, ActionsheetBackdrop, ActionsheetContent, ActionsheetDragIndicator, ActionsheetDragIndicatorWrapper } from '../ui/actionsheet';
1817
import { HStack } from '../ui/hstack';
@@ -33,6 +32,7 @@ export const LiveKitBottomSheet = () => {
3332
const { trackEvent } = useAnalytics();
3433

3534
const [currentView, setCurrentView] = useState<BottomSheetView>(BottomSheetView.ROOM_SELECT);
35+
const [previousView, setPreviousView] = useState<BottomSheetView | null>(null);
3636
const [isMuted, setIsMuted] = useState(true); // Default to muted
3737
const [permissionsRequested, setPermissionsRequested] = useState(false);
3838

@@ -156,28 +156,17 @@ export const LiveKitBottomSheet = () => {
156156
const speaker = selectedAudioDevices.speaker;
157157
console.log('Updating audio routing for:', speaker.type);
158158

159+
let targetType: 'bluetooth' | 'speaker' | 'earpiece' | 'default' = 'default';
160+
159161
if (speaker.type === 'speaker') {
160-
// Force Speakerphone
161-
await Audio.setAudioModeAsync({
162-
allowsRecordingIOS: true,
163-
staysActiveInBackground: true,
164-
playsInSilentModeIOS: true,
165-
shouldDuckAndroid: true,
166-
playThroughEarpieceAndroid: false, // This forces speaker on Android
167-
interruptionModeIOS: InterruptionModeIOS.DoNotMix,
168-
});
162+
targetType = 'speaker';
163+
} else if (speaker.type === 'bluetooth') {
164+
targetType = 'bluetooth';
169165
} else {
170-
// Default (Earpiece) or Bluetooth
171-
// For Bluetooth to work, we usually just need to NOT force speaker.
172-
await Audio.setAudioModeAsync({
173-
allowsRecordingIOS: true,
174-
staysActiveInBackground: true,
175-
playsInSilentModeIOS: true,
176-
shouldDuckAndroid: true,
177-
playThroughEarpieceAndroid: true, // This allows earpiece/bluetooth
178-
interruptionModeIOS: InterruptionModeIOS.DoNotMix,
179-
});
166+
targetType = 'earpiece';
180167
}
168+
169+
await applyAudioRouting(targetType);
181170
} catch (error) {
182171
console.error('Failed to update audio routing:', error);
183172
}
@@ -221,12 +210,18 @@ export const LiveKitBottomSheet = () => {
221210
}, [disconnectFromRoom]);
222211

223212
const handleShowAudioSettings = useCallback(() => {
213+
setPreviousView(currentView);
224214
setCurrentView(BottomSheetView.AUDIO_SETTINGS);
225-
}, []);
215+
}, [currentView]);
226216

227217
const handleBackFromAudioSettings = useCallback(() => {
228-
setCurrentView(BottomSheetView.CONNECTED);
229-
}, []);
218+
if (previousView) {
219+
setCurrentView(previousView);
220+
setPreviousView(null);
221+
} else {
222+
setCurrentView(isConnected && currentRoomInfo ? BottomSheetView.CONNECTED : BottomSheetView.ROOM_SELECT);
223+
}
224+
}, [previousView, isConnected, currentRoomInfo]);
230225

231226
const renderRoomSelect = () => (
232227
<View style={styles.content}>

src/components/settings/audio-device-selection.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,9 @@ export const AudioDeviceSelection: React.FC<AudioDeviceSelectionProps> = ({ show
7070
);
7171
};
7272

73-
const availableMicrophones = availableAudioDevices.filter(
74-
(device) => device.isAvailable && (device.type === 'default' || device.type === 'microphone' || device.type === 'bluetooth' || device.type === 'wired')
75-
);
73+
const availableMicrophones = availableAudioDevices.filter((device) => device.isAvailable && (device.type === 'default' || device.type === 'microphone' || device.type === 'bluetooth' || device.type === 'wired'));
7674

77-
const availableSpeakers = availableAudioDevices.filter(
78-
(device) => device.isAvailable && (device.type === 'default' || device.type === 'speaker' || device.type === 'bluetooth' || device.type === 'wired')
79-
);
75+
const availableSpeakers = availableAudioDevices.filter((device) => device.isAvailable && (device.type === 'default' || device.type === 'speaker' || device.type === 'bluetooth' || device.type === 'wired'));
8076

8177
return (
8278
<ScrollView className="flex-1">

0 commit comments

Comments
 (0)