feat: Phase 4 — Live, Vitals, Zones, MAT, Settings screens
LiveScreen: GaussianSplatWebView + gaussian-splats.html (Three.js 3D viz), LiveHUD VitalsScreen: BreathingGauge, HeartRateGauge, MetricCard (Reanimated arcs) ZonesScreen: FloorPlanSvg (SVG heatmap 20x20), ZoneLegend, useOccupancyGrid MATScreen: MatWebView + mat-dashboard.html (pure-JS disaster response), AlertCard/List, SurvivorCounter SettingsScreen: ServerUrlInput (URL validation + test), ThemePicker, RssiToggle Verified: tsc 0 errors, jest passes
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Linking, ScrollView, View } from 'react-native';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { colors } from '@/theme/colors';
|
||||
import { spacing } from '@/theme/spacing';
|
||||
import { WS_PATH } from '@/constants/websocket';
|
||||
import { apiService } from '@/services/api.service';
|
||||
import { wsService } from '@/services/ws.service';
|
||||
import { useSettingsStore } from '@/stores/settingsStore';
|
||||
import { Alert, Pressable, Platform } from 'react-native';
|
||||
import { ThemePicker } from './ThemePicker';
|
||||
import { RssiToggle } from './RssiToggle';
|
||||
import { ServerUrlInput } from './ServerUrlInput';
|
||||
|
||||
type GlowCardProps = {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const GlowCard = ({ title, children }: GlowCardProps) => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: '#0F141E',
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: `${colors.accent}35`,
|
||||
padding: spacing.md,
|
||||
marginBottom: spacing.md,
|
||||
}}
|
||||
>
|
||||
<ThemedText preset="labelMd" style={{ marginBottom: spacing.sm, color: colors.textPrimary }}>
|
||||
{title}
|
||||
</ThemedText>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const ScanIntervalPicker = ({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
}) => {
|
||||
const options = [1, 2, 5];
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', gap: spacing.sm, marginTop: spacing.sm }}>
|
||||
{options.map((interval) => {
|
||||
const isActive = interval === value;
|
||||
return (
|
||||
<Pressable
|
||||
key={interval}
|
||||
onPress={() => onChange(interval)}
|
||||
style={{
|
||||
flex: 1,
|
||||
borderWidth: 1,
|
||||
borderColor: isActive ? colors.accent : colors.border,
|
||||
borderRadius: 8,
|
||||
backgroundColor: isActive ? `${colors.accent}20` : colors.surface,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<ThemedText
|
||||
preset="bodySm"
|
||||
style={{
|
||||
color: isActive ? colors.accent : colors.textSecondary,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
{interval}s
|
||||
</ThemedText>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const SettingsScreen = () => {
|
||||
const serverUrl = useSettingsStore((state) => state.serverUrl);
|
||||
const rssiScanEnabled = useSettingsStore((state) => state.rssiScanEnabled);
|
||||
const theme = useSettingsStore((state) => state.theme);
|
||||
const setServerUrl = useSettingsStore((state) => state.setServerUrl);
|
||||
const setRssiScanEnabled = useSettingsStore((state) => state.setRssiScanEnabled);
|
||||
const setTheme = useSettingsStore((state) => state.setTheme);
|
||||
|
||||
const [draftUrl, setDraftUrl] = useState(serverUrl);
|
||||
const [scanInterval, setScanInterval] = useState(2);
|
||||
|
||||
useEffect(() => {
|
||||
setDraftUrl(serverUrl);
|
||||
}, [serverUrl]);
|
||||
|
||||
const intervalSummary = useMemo(() => `${scanInterval}s`, [scanInterval]);
|
||||
|
||||
const handleSaveUrl = () => {
|
||||
setServerUrl(draftUrl);
|
||||
apiService.setBaseUrl(draftUrl);
|
||||
wsService.disconnect();
|
||||
wsService.connect(draftUrl);
|
||||
};
|
||||
|
||||
const handleOpenGitHub = async () => {
|
||||
const handled = await Linking.canOpenURL('https://github.com');
|
||||
if (!handled) {
|
||||
Alert.alert('Unable to open link', 'Please open https://github.com manually in your browser.');
|
||||
return;
|
||||
}
|
||||
|
||||
await Linking.openURL('https://github.com');
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemedView style={{ flex: 1, backgroundColor: colors.bg, padding: spacing.md }}>
|
||||
<ScrollView
|
||||
contentContainerStyle={{
|
||||
paddingBottom: spacing.xl,
|
||||
}}
|
||||
>
|
||||
<GlowCard title="SERVER">
|
||||
<ServerUrlInput value={draftUrl} onChange={setDraftUrl} onSave={handleSaveUrl} />
|
||||
</GlowCard>
|
||||
|
||||
<GlowCard title="SENSING">
|
||||
<RssiToggle enabled={rssiScanEnabled} onChange={setRssiScanEnabled} />
|
||||
<ThemedText preset="bodyMd" style={{ marginTop: spacing.md }}>
|
||||
Scan interval
|
||||
</ThemedText>
|
||||
<ScanIntervalPicker value={scanInterval} onChange={setScanInterval} />
|
||||
<ThemedText preset="bodySm" style={{ color: colors.textSecondary, marginTop: spacing.sm }}>
|
||||
Active interval: {intervalSummary}
|
||||
</ThemedText>
|
||||
{Platform.OS === 'ios' && (
|
||||
<ThemedText preset="bodySm" style={{ color: colors.textSecondary, marginTop: spacing.sm }}>
|
||||
iOS: RSSI scanning uses stubbed telemetry in this build.
|
||||
</ThemedText>
|
||||
)}
|
||||
</GlowCard>
|
||||
|
||||
<GlowCard title="APPEARANCE">
|
||||
<ThemePicker value={theme} onChange={setTheme} />
|
||||
</GlowCard>
|
||||
|
||||
<GlowCard title="ABOUT">
|
||||
<ThemedText preset="bodyMd" style={{ marginBottom: spacing.xs }}>
|
||||
WiFi-DensePose Mobile v1.0.0
|
||||
</ThemedText>
|
||||
<ThemedText
|
||||
preset="bodySm"
|
||||
style={{ color: colors.accent, marginBottom: spacing.sm }}
|
||||
onPress={handleOpenGitHub}
|
||||
>
|
||||
View on GitHub
|
||||
</ThemedText>
|
||||
<ThemedText preset="bodySm">WebSocket: {WS_PATH}</ThemedText>
|
||||
<ThemedText preset="bodySm" style={{ color: colors.textSecondary }}>
|
||||
Triage priority mapping: Immediate/Delayed/Minor/Deceased/Unknown
|
||||
</ThemedText>
|
||||
</GlowCard>
|
||||
</ScrollView>
|
||||
</ThemedView>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsScreen;
|
||||
|
||||
Reference in New Issue
Block a user