Skip to content

Commit 102f317

Browse files
authored
Merge pull request #227 from Resgrid/develop
RU-T47 Trying fix PTT voice call connection issue
2 parents 7f3a6f2 + 6ced4b9 commit 102f317

File tree

13 files changed

+222
-116
lines changed

13 files changed

+222
-116
lines changed

__mocks__/mapbox-gl.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Mock for mapbox-gl in Jest tests
2+
module.exports = {
3+
accessToken: '',
4+
Map: jest.fn().mockImplementation(() => ({
5+
on: jest.fn(),
6+
remove: jest.fn(),
7+
addControl: jest.fn(),
8+
removeControl: jest.fn(),
9+
setCenter: jest.fn(),
10+
setZoom: jest.fn(),
11+
flyTo: jest.fn(),
12+
easeTo: jest.fn(),
13+
jumpTo: jest.fn(),
14+
resize: jest.fn(),
15+
getCanvas: jest.fn(() => ({
16+
style: {},
17+
})),
18+
getStyle: jest.fn(() => ({})),
19+
setStyle: jest.fn(),
20+
})),
21+
Marker: jest.fn().mockImplementation(() => ({
22+
setLngLat: jest.fn().mockReturnThis(),
23+
addTo: jest.fn().mockReturnThis(),
24+
remove: jest.fn(),
25+
getLngLat: jest.fn(),
26+
setPopup: jest.fn().mockReturnThis(),
27+
})),
28+
Popup: jest.fn().mockImplementation(() => ({
29+
setLngLat: jest.fn().mockReturnThis(),
30+
setHTML: jest.fn().mockReturnThis(),
31+
addTo: jest.fn().mockReturnThis(),
32+
remove: jest.fn(),
33+
})),
34+
NavigationControl: jest.fn(),
35+
GeolocateControl: jest.fn(),
36+
ScaleControl: jest.fn(),
37+
FullscreenControl: jest.fn(),
38+
};

__mocks__/styleMock.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Mock for CSS/Style imports in Jest tests
2+
module.exports = {};

jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ module.exports = {
3030
],
3131
coverageDirectory: '<rootDir>/coverage/',
3232
moduleNameMapper: {
33-
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
33+
'\\.(css|less|scss|sass)$': '<rootDir>/__mocks__/styleMock.js',
34+
'mapbox-gl': '<rootDir>/__mocks__/mapbox-gl.js',
3435
},
3536
};

src/app/(app)/_layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ export default function TabLayout() {
409409
<DrawerBackdrop onPress={handleCloseDrawer} />
410410
<DrawerContent className="w-4/5 bg-white p-1 dark:bg-gray-900">
411411
<DrawerBody>
412-
<Sidebar />
412+
<Sidebar onClose={handleCloseDrawer} />
413413
</DrawerBody>
414414
</DrawerContent>
415415
</Drawer>

src/app/_layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import '../lib/i18n';
44

55
import { Env } from '@env';
66
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
7+
import { registerGlobals } from '@livekit/react-native';
78
import notifee from '@notifee/react-native';
89
import { createNavigationContainerRef, DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
910
import * as Sentry from '@sentry/react-native';
@@ -68,7 +69,6 @@ Sentry.init({
6869
// Only register LiveKit globals on native platforms
6970
// Web/Electron uses livekit-client which handles WebRTC natively
7071
if (Platform.OS !== 'web') {
71-
const { registerGlobals } = require('@livekit/react-native');
7272
registerGlobals();
7373
}
7474

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ import { t } from 'i18next';
22
import { Headphones, Mic, MicOff, PhoneOff, Settings } from 'lucide-react-native';
33
import { useColorScheme } from 'nativewind';
44
import React, { useCallback, useEffect, useRef, useState } from 'react';
5-
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
5+
import { ActivityIndicator, ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
66

77
import { useAnalytics } from '@/hooks/use-analytics';
88
import { type DepartmentVoiceChannelResultData } from '@/models/v4/voice/departmentVoiceResultData';
99
import { useBluetoothAudioStore } from '@/stores/app/bluetooth-audio-store';
10+
import { applyAudioRouting, requestAndroidPhonePermissions, useLiveKitStore } from '@/stores/app/livekit-store';
1011

11-
import { Card } from '../../components/ui/card';
12-
import { Text } from '../../components/ui/text';
13-
import { applyAudioRouting, useLiveKitStore } from '../../stores/app/livekit-store';
1412
import { AudioDeviceSelection } from '../settings/audio-device-selection';
1513
import { Actionsheet, ActionsheetBackdrop, ActionsheetContent, ActionsheetDragIndicator, ActionsheetDragIndicatorWrapper } from '../ui/actionsheet';
14+
import { Card } from '../ui/card';
1615
import { HStack } from '../ui/hstack';
16+
import { Text } from '../ui/text';
1717
import { VStack } from '../ui/vstack';
1818

1919
export enum BottomSheetView {
@@ -86,6 +86,11 @@ export const LiveKitBottomSheet = () => {
8686
// If we're showing the sheet, make sure we have the latest rooms
8787
if (isBottomSheetVisible && currentView === BottomSheetView.ROOM_SELECT) {
8888
fetchVoiceSettings();
89+
// Pre-warm Android phone-state permissions (READ_PHONE_STATE / READ_PHONE_NUMBERS)
90+
// while the user is browsing the room list. The system dialog, if any, appears
91+
// here in a clean window instead of blocking the Join flow later. On subsequent
92+
// opens this is an instant no-op (permissions already granted).
93+
void requestAndroidPhonePermissions();
8994
}
9095
}, [isBottomSheetVisible, currentView, fetchVoiceSettings]);
9196

@@ -162,7 +167,12 @@ export const LiveKitBottomSheet = () => {
162167

163168
const renderRoomSelect = () => (
164169
<View style={styles.content}>
165-
{availableRooms.length === 0 ? (
170+
{isConnecting ? (
171+
<View className="flex-1 items-center justify-center" testID="connecting-indicator">
172+
<ActivityIndicator size="large" />
173+
<Text className="mt-3 text-center text-base font-medium text-gray-500">{t('livekit.connecting')}</Text>
174+
</View>
175+
) : availableRooms.length === 0 ? (
166176
<View className="flex-1 items-center justify-center">
167177
<Text className="text-center text-lg font-medium text-gray-500">{t('livekit.no_rooms_available')}</Text>
168178
</View>

src/components/maps/map-view.web.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ export const StyleURL = {
2828
};
2929

3030
// UserTrackingMode enum matching native SDK
31-
export const UserTrackingMode = {
32-
Follow: 'follow',
33-
FollowWithHeading: 'followWithHeading',
34-
FollowWithCourse: 'followWithCourse',
35-
};
31+
export enum UserTrackingMode {
32+
Follow = 'normal',
33+
FollowWithHeading = 'compass',
34+
FollowWithCourse = 'course',
35+
}
3636

3737
// Access token setter for compatibility
3838
export const setAccessToken = (token: string) => {

src/components/maps/mapbox.ts

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,54 @@
44
*/
55
import { Platform } from 'react-native';
66

7+
import * as MapboxNative from './map-view.native';
8+
import * as MapboxWeb from './map-view.web';
9+
710
// Import the platform-specific implementation
811
// Metro bundler will resolve to the correct file based on platform
9-
const MapboxImpl = Platform.OS === 'web' ? require('./map-view.web').default : require('./map-view.native').default;
12+
const MapboxImpl = Platform.OS === 'web' ? MapboxWeb.default : MapboxNative.default;
1013

1114
// Re-export all components
1215
export const MapView = MapboxImpl.MapView || MapboxImpl;
13-
export const Camera = Platform.OS === 'web' ? require('./map-view.web').Camera : require('./map-view.native').Camera;
14-
export const PointAnnotation = Platform.OS === 'web' ? require('./map-view.web').PointAnnotation : require('./map-view.native').PointAnnotation;
15-
export const UserLocation = Platform.OS === 'web' ? require('./map-view.web').UserLocation : require('./map-view.native').UserLocation;
16-
export const MarkerView = Platform.OS === 'web' ? require('./map-view.web').MarkerView : require('./map-view.native').MarkerView;
17-
export const ShapeSource = Platform.OS === 'web' ? require('./map-view.web').ShapeSource : require('./map-view.native').ShapeSource;
18-
export const SymbolLayer = Platform.OS === 'web' ? require('./map-view.web').SymbolLayer : require('./map-view.native').SymbolLayer;
19-
export const CircleLayer = Platform.OS === 'web' ? require('./map-view.web').CircleLayer : require('./map-view.native').CircleLayer;
20-
export const LineLayer = Platform.OS === 'web' ? require('./map-view.web').LineLayer : require('./map-view.native').LineLayer;
21-
export const FillLayer = Platform.OS === 'web' ? require('./map-view.web').FillLayer : require('./map-view.native').FillLayer;
22-
export const Images = Platform.OS === 'web' ? require('./map-view.web').Images : require('./map-view.native').Images;
23-
export const Callout = Platform.OS === 'web' ? require('./map-view.web').Callout : require('./map-view.native').Callout;
16+
export const Camera = Platform.OS === 'web' ? MapboxWeb.Camera : MapboxNative.Camera;
17+
export const PointAnnotation = Platform.OS === 'web' ? MapboxWeb.PointAnnotation : MapboxNative.PointAnnotation;
18+
export const UserLocation = Platform.OS === 'web' ? MapboxWeb.UserLocation : MapboxNative.UserLocation;
19+
export const MarkerView = Platform.OS === 'web' ? MapboxWeb.MarkerView : MapboxNative.MarkerView;
20+
export const ShapeSource = Platform.OS === 'web' ? MapboxWeb.ShapeSource : MapboxNative.ShapeSource;
21+
export const SymbolLayer = Platform.OS === 'web' ? MapboxWeb.SymbolLayer : MapboxNative.SymbolLayer;
22+
export const CircleLayer = Platform.OS === 'web' ? MapboxWeb.CircleLayer : MapboxNative.CircleLayer;
23+
export const LineLayer = Platform.OS === 'web' ? MapboxWeb.LineLayer : MapboxNative.LineLayer;
24+
export const FillLayer = Platform.OS === 'web' ? MapboxWeb.FillLayer : MapboxNative.FillLayer;
25+
export const Images = Platform.OS === 'web' ? MapboxWeb.Images : MapboxNative.Images;
26+
export const Callout = Platform.OS === 'web' ? MapboxWeb.Callout : MapboxNative.Callout;
2427

2528
// Export style URL constants
26-
export const StyleURL = Platform.OS === 'web' ? require('./map-view.web').StyleURL : require('./map-view.native').StyleURL;
29+
export const StyleURL = Platform.OS === 'web' ? MapboxWeb.StyleURL : MapboxNative.StyleURL;
2730

2831
// Export UserTrackingMode
29-
export const UserTrackingMode = Platform.OS === 'web' ? require('./map-view.web').UserTrackingMode : require('./map-view.native').UserTrackingMode;
32+
export const UserTrackingMode = Platform.OS === 'web' ? MapboxWeb.UserTrackingMode : MapboxNative.UserTrackingMode;
3033

3134
// Export setAccessToken
32-
export const setAccessToken = Platform.OS === 'web' ? require('./map-view.web').setAccessToken : require('./map-view.native').setAccessToken;
35+
export const setAccessToken = Platform.OS === 'web' ? MapboxWeb.setAccessToken : MapboxNative.setAccessToken;
36+
37+
// Default export matching Mapbox structure with all properties
38+
const Mapbox = {
39+
...MapboxImpl,
40+
MapView: MapView,
41+
Camera: Camera,
42+
PointAnnotation: PointAnnotation,
43+
UserLocation: UserLocation,
44+
MarkerView: MarkerView,
45+
ShapeSource: ShapeSource,
46+
SymbolLayer: SymbolLayer,
47+
CircleLayer: CircleLayer,
48+
LineLayer: LineLayer,
49+
FillLayer: FillLayer,
50+
Images: Images,
51+
Callout: Callout,
52+
StyleURL: StyleURL,
53+
UserTrackingMode: UserTrackingMode,
54+
setAccessToken: setAccessToken,
55+
};
3356

34-
// Default export matching Mapbox structure
35-
export default MapboxImpl;
57+
export default Mapbox;

src/components/sidebar/sidebar-content.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import { SidebarRolesCard } from './roles-sidebar';
1818
import { SidebarStatusCard } from './status-sidebar';
1919
import { SidebarUnitCard } from './unit-sidebar';
2020

21-
const Sidebar = () => {
21+
interface SidebarProps {
22+
onClose?: () => void;
23+
}
24+
25+
const Sidebar = ({ onClose }: SidebarProps) => {
2226
const activeStatuses = useCoreStore((state) => state.activeStatuses);
2327
const setIsOpen = useStatusBottomSheetStore((state) => state.setIsOpen);
2428
const { t } = useTranslation();
@@ -27,6 +31,7 @@ const Sidebar = () => {
2731
const isActiveStatusesEmpty = !activeStatuses?.Statuses || activeStatuses.Statuses.length === 0;
2832

2933
const handleNavigateToSettings = () => {
34+
onClose?.();
3035
router.push('/settings');
3136
};
3237

src/components/sidebar/status-sidebar.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import { useCoreStore } from '@/stores/app/core-store';
55

66
import { Card } from '../ui/card';
77

8-
type ItemProps = {};
9-
108
export const SidebarStatusCard = () => {
119
const activeUnitStatus = useCoreStore((state) => state.activeUnitStatus);
1210

0 commit comments

Comments
 (0)