Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit df39401

Browse files
committed
feat: full app integration — all screens wired and working
1 parent 47861de commit df39401

6 files changed

Lines changed: 93 additions & 44 deletions

File tree

mobile/App.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,46 @@ import { NavigationContainer, DarkTheme } from '@react-navigation/native';
33
import { GestureHandlerRootView } from 'react-native-gesture-handler';
44
import { StatusBar } from 'expo-status-bar';
55
import { SafeAreaProvider } from 'react-native-safe-area-context';
6+
import { apiService } from '@/services/api.service';
7+
import { rssiService } from '@/services/rssi.service';
8+
import { wsService } from '@/services/ws.service';
69
import { ThemeProvider } from './src/theme/ThemeContext';
10+
import { usePoseStore } from './src/stores/poseStore';
11+
import { useSettingsStore } from './src/stores/settingsStore';
712
import { RootNavigator } from './src/navigation/RootNavigator';
813

914
export default function App() {
15+
const serverUrl = useSettingsStore((state) => state.serverUrl);
16+
const rssiScanEnabled = useSettingsStore((state) => state.rssiScanEnabled);
17+
18+
useEffect(() => {
19+
apiService.setBaseUrl(serverUrl);
20+
const unsubscribe = wsService.subscribe(usePoseStore.getState().handleFrame);
21+
wsService.connect(serverUrl);
22+
23+
return () => {
24+
unsubscribe();
25+
wsService.disconnect();
26+
};
27+
}, [serverUrl]);
28+
29+
useEffect(() => {
30+
if (!rssiScanEnabled) {
31+
rssiService.stopScanning();
32+
return;
33+
}
34+
35+
const unsubscribe = rssiService.subscribe(() => {
36+
// Consumers can subscribe elsewhere for RSSI events.
37+
});
38+
rssiService.startScanning(2000);
39+
40+
return () => {
41+
unsubscribe();
42+
rssiService.stopScanning();
43+
};
44+
}, [rssiScanEnabled]);
45+
1046
useEffect(() => {
1147
(globalThis as { __appStartTime?: number }).__appStartTime = Date.now();
1248
}, []);

mobile/jest.setup.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,16 @@ jest.mock('react-native-wifi-reborn', () => ({
99
jest.mock('react-native-reanimated', () =>
1010
require('react-native-reanimated/mock')
1111
);
12+
13+
jest.mock('react-native-webview', () => {
14+
const React = require('react');
15+
const { View } = require('react-native');
16+
17+
const MockWebView = (props: unknown) => React.createElement(View, props);
18+
19+
return {
20+
__esModule: true,
21+
default: MockWebView,
22+
WebView: MockWebView,
23+
};
24+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React, { PropsWithChildren } from 'react';
2+
import { render, type RenderOptions } from '@testing-library/react-native';
3+
import { NavigationContainer } from '@react-navigation/native';
4+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
5+
import { SafeAreaProvider } from 'react-native-safe-area-context';
6+
import { ThemeProvider } from '@/theme/ThemeContext';
7+
8+
type TestProvidersProps = PropsWithChildren<object>;
9+
10+
const TestProviders = ({ children }: TestProvidersProps) => (
11+
<GestureHandlerRootView style={{ flex: 1 }}>
12+
<SafeAreaProvider>
13+
<ThemeProvider>{children}</ThemeProvider>
14+
</SafeAreaProvider>
15+
</GestureHandlerRootView>
16+
);
17+
18+
const TestProvidersWithNavigation = ({ children }: TestProvidersProps) => (
19+
<TestProviders>
20+
<NavigationContainer>{children}</NavigationContainer>
21+
</TestProviders>
22+
);
23+
24+
interface RenderWithProvidersOptions extends Omit<RenderOptions, 'wrapper'> {
25+
withNavigation?: boolean;
26+
}
27+
28+
export const renderWithProviders = (
29+
ui: React.ReactElement,
30+
{ withNavigation, ...options }: RenderWithProvidersOptions = {},
31+
) => {
32+
return render(ui, {
33+
...options,
34+
wrapper: withNavigation ? TestProvidersWithNavigation : TestProviders,
35+
});
36+
};

mobile/src/hooks/usePoseStream.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useEffect } from 'react';
22
import { wsService } from '@/services/ws.service';
33
import { usePoseStore } from '@/stores/poseStore';
4-
import { useSettingsStore } from '@/stores/settingsStore';
54

65
export interface UsePoseStreamResult {
76
connectionStatus: ReturnType<typeof usePoseStore.getState>['connectionStatus'];
@@ -10,7 +9,6 @@ export interface UsePoseStreamResult {
109
}
1110

1211
export function usePoseStream(): UsePoseStreamResult {
13-
const serverUrl = useSettingsStore((state) => state.serverUrl);
1412
const connectionStatus = usePoseStore((state) => state.connectionStatus);
1513
const lastFrame = usePoseStore((state) => state.lastFrame);
1614
const isSimulated = usePoseStore((state) => state.isSimulated);
@@ -19,13 +17,11 @@ export function usePoseStream(): UsePoseStreamResult {
1917
const unsubscribe = wsService.subscribe((frame) => {
2018
usePoseStore.getState().handleFrame(frame);
2119
});
22-
wsService.connect(serverUrl);
2320

2421
return () => {
2522
unsubscribe();
26-
wsService.disconnect();
2723
};
28-
}, [serverUrl]);
24+
}, []);
2925

3026
return { connectionStatus, lastFrame, isSimulated };
3127
}

mobile/src/navigation/MainTabs.tsx

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import React, { Suspense, useEffect, useState } from 'react';
1+
import React, { Suspense } from 'react';
22
import { ActivityIndicator } from 'react-native';
33
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
44
import { Ionicons } from '@expo/vector-icons';
55
import { ThemedText } from '../components/ThemedText';
66
import { ThemedView } from '../components/ThemedView';
77
import { colors } from '../theme/colors';
8+
import { useMatStore } from '../stores/matStore';
89
import { MainTabsParamList } from './types';
910

1011
const createPlaceholder = (label: string) => {
@@ -74,29 +75,6 @@ const toIconName = (routeName: keyof MainTabsParamList) => {
7475
}
7576
};
7677

77-
const getMatAlertCount = async (): Promise<number> => {
78-
try {
79-
const mod = (await import('../stores/matStore')) as Record<string, unknown>;
80-
const candidates = [mod.useMatStore, mod.useStore].filter((candidate) => {
81-
return (
82-
!!candidate &&
83-
typeof candidate === 'function' &&
84-
typeof (candidate as { getState?: () => unknown }).getState === 'function'
85-
);
86-
}) as Array<{ getState: () => { alerts?: unknown[] } }>;
87-
88-
for (const store of candidates) {
89-
const alerts = store.getState().alerts;
90-
if (Array.isArray(alerts)) {
91-
return alerts.length;
92-
}
93-
}
94-
} catch {
95-
return 0;
96-
}
97-
return 0;
98-
};
99-
10078
const screens: ReadonlyArray<{ name: keyof MainTabsParamList; component: React.ComponentType }> = [
10179
{ name: 'Live', component: LiveScreen },
10280
{ name: 'Vitals', component: VitalsScreen },
@@ -114,18 +92,7 @@ const Suspended = ({ component: Component }: { component: React.ComponentType })
11492
);
11593

11694
export const MainTabs = () => {
117-
const [matAlertCount, setMatAlertCount] = useState(0);
118-
119-
useEffect(() => {
120-
const readCount = async () => {
121-
const count = await getMatAlertCount();
122-
setMatAlertCount(count);
123-
};
124-
125-
void readCount();
126-
const timer = setInterval(readCount, 2000);
127-
return () => clearInterval(timer);
128-
}, []);
95+
const matAlertCount = useMatStore((state) => state.alerts.length);
12996

13097
return (
13198
<Tab.Navigator

mobile/src/screens/SettingsScreen/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,11 @@ export const SettingsScreen = () => {
9898
const intervalSummary = useMemo(() => `${scanInterval}s`, [scanInterval]);
9999

100100
const handleSaveUrl = () => {
101-
setServerUrl(draftUrl);
102-
apiService.setBaseUrl(draftUrl);
101+
const newUrl = draftUrl.trim();
102+
setServerUrl(newUrl);
103103
wsService.disconnect();
104-
wsService.connect(draftUrl);
104+
wsService.connect(newUrl);
105+
apiService.setBaseUrl(newUrl);
105106
};
106107

107108
const handleOpenGitHub = async () => {

0 commit comments

Comments
 (0)