From 779bf8ff4391a19db1452c8f0fae18fb396e8b36 Mon Sep 17 00:00:00 2001 From: Yossi Elkrief Date: Mon, 2 Mar 2026 12:53:45 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=203=20=E2=80=94=20services,=20sto?= =?UTF-8?q?res,=20navigation,=20design=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Services: ws.service, api.service, simulation.service, rssi.service (android+ios) Stores: poseStore, settingsStore, matStore (Zustand) Types: sensing, mat, api, navigation Hooks: usePoseStream, useRssiScanner, useServerReachability Theme: colors, typography, spacing, ThemeContext Navigation: MainTabs (5 tabs), RootNavigator, types Components: GaugeArc, SparklineChart, OccupancyGrid, StatusDot, ConnectionBanner, SignalBar, +more Utils: ringBuffer, colorMap, formatters, urlValidator Verified: tsc 0 errors, jest passes --- mobile/App.tsx | 51 ++++-- mobile/src/components/ConnectionBanner.tsx | 70 ++++++++ mobile/src/components/ErrorBoundary.tsx | 66 +++++++ mobile/src/components/GaugeArc.tsx | 96 ++++++++++ mobile/src/components/LoadingSpinner.tsx | 60 +++++++ mobile/src/components/ModeBadge.tsx | 71 ++++++++ mobile/src/components/OccupancyGrid.tsx | 147 ++++++++++++++++ mobile/src/components/SignalBar.tsx | 62 +++++++ mobile/src/components/SparklineChart.tsx | 64 +++++++ mobile/src/components/StatusDot.tsx | 83 +++++++++ mobile/src/components/ThemedText.tsx | 28 +++ mobile/src/components/ThemedView.tsx | 24 +++ mobile/src/constants/api.ts | 14 ++ mobile/src/constants/simulation.ts | 20 +++ mobile/src/constants/websocket.ts | 3 + mobile/src/hooks/usePoseStream.ts | 31 ++++ mobile/src/hooks/useRssiScanner.ts | 31 ++++ mobile/src/hooks/useServerReachability.ts | 52 ++++++ mobile/src/hooks/useTheme.ts | 4 + mobile/src/navigation/MainTabs.tsx | 162 +++++++++++++++++ mobile/src/navigation/RootNavigator.tsx | 5 + mobile/src/navigation/types.ts | 11 ++ mobile/src/services/api.service.ts | 92 ++++++++++ mobile/src/services/rssi.service.android.ts | 62 +++++++ mobile/src/services/rssi.service.ios.ts | 41 +++++ mobile/src/services/rssi.service.ts | 19 ++ mobile/src/services/simulation.service.ts | 107 ++++++++++++ mobile/src/services/ws.service.ts | 164 ++++++++++++++++++ mobile/src/stores/matStore.ts | 74 ++++++++ mobile/src/stores/poseStore.ts | 71 ++++++++ mobile/src/stores/settingsStore.ts | 47 +++++ mobile/src/theme/ThemeContext.tsx | 74 ++++++++ mobile/src/theme/colors.ts | 22 +++ mobile/src/theme/index.ts | 4 + mobile/src/theme/spacing.ts | 10 ++ mobile/src/theme/typography.ts | 26 +++ mobile/src/types/api.ts | 39 +++++ mobile/src/types/mat.ts | 93 ++++++++++ mobile/src/types/navigation.ts | 17 ++ .../src/types/react-native-wifi-reborn.d.ts | 14 ++ mobile/src/types/sensing.ts | 50 ++++++ mobile/src/utils/colorMap.ts | 21 +++ mobile/src/utils/formatters.ts | 34 ++++ mobile/src/utils/ringBuffer.ts | 48 +++++ mobile/src/utils/urlValidator.ts | 25 +++ 45 files changed, 2290 insertions(+), 19 deletions(-) create mode 100644 mobile/src/types/react-native-wifi-reborn.d.ts diff --git a/mobile/App.tsx b/mobile/App.tsx index 64709bc..4b452af 100644 --- a/mobile/App.tsx +++ b/mobile/App.tsx @@ -1,25 +1,38 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import { useEffect } from 'react'; +import { NavigationContainer, DarkTheme } from '@react-navigation/native'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { StatusBar } from 'expo-status-bar'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { ThemeProvider } from './src/theme/ThemeContext'; +import { RootNavigator } from './src/navigation/RootNavigator'; export default function App() { + useEffect(() => { + (globalThis as { __appStartTime?: number }).__appStartTime = Date.now(); + }, []); + + const navigationTheme = { + ...DarkTheme, + colors: { + ...DarkTheme.colors, + background: '#0A0E1A', + card: '#0D1117', + text: '#E2E8F0', + border: '#1E293B', + primary: '#32B8C6', + }, + }; + return ( - - WiFi-DensePose - - + + + + + + + + + + ); } - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: '#fff', - }, - title: { - fontSize: 24, - fontWeight: '600', - }, -}); diff --git a/mobile/src/components/ConnectionBanner.tsx b/mobile/src/components/ConnectionBanner.tsx index e69de29..9ab6601 100644 --- a/mobile/src/components/ConnectionBanner.tsx +++ b/mobile/src/components/ConnectionBanner.tsx @@ -0,0 +1,70 @@ +import { StyleSheet, View } from 'react-native'; +import { ThemedText } from './ThemedText'; + +type ConnectionState = 'connected' | 'simulated' | 'disconnected'; + +type ConnectionBannerProps = { + status: ConnectionState; +}; + +const resolveState = (status: ConnectionState) => { + if (status === 'connected') { + return { + label: 'LIVE STREAM', + backgroundColor: '#0F6B2A', + textColor: '#E2FFEA', + }; + } + + if (status === 'disconnected') { + return { + label: 'DISCONNECTED', + backgroundColor: '#8A1E2A', + textColor: '#FFE3E7', + }; + } + + return { + label: 'SIMULATED DATA', + backgroundColor: '#9A5F0C', + textColor: '#FFF3E1', + }; +}; + +export const ConnectionBanner = ({ status }: ConnectionBannerProps) => { + const state = resolveState(status); + + return ( + + + {state.label} + + + ); +}; + +const styles = StyleSheet.create({ + banner: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + zIndex: 100, + paddingVertical: 6, + borderBottomWidth: 2, + alignItems: 'center', + justifyContent: 'center', + }, + text: { + letterSpacing: 2, + fontWeight: '700', + }, +}); diff --git a/mobile/src/components/ErrorBoundary.tsx b/mobile/src/components/ErrorBoundary.tsx index e69de29..9f1d03a 100644 --- a/mobile/src/components/ErrorBoundary.tsx +++ b/mobile/src/components/ErrorBoundary.tsx @@ -0,0 +1,66 @@ +import { Component, ErrorInfo, ReactNode } from 'react'; +import { Button, StyleSheet, View } from 'react-native'; +import { ThemedText } from './ThemedText'; +import { ThemedView } from './ThemedView'; + +type ErrorBoundaryProps = { + children: ReactNode; +}; + +type ErrorBoundaryState = { + hasError: boolean; + error?: Error; +}; + +export class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('ErrorBoundary caught an error', error, errorInfo); + } + + handleRetry = () => { + this.setState({ hasError: false, error: undefined }); + }; + + render() { + if (this.state.hasError) { + return ( + + Something went wrong + + {this.state.error?.message ?? 'An unexpected error occurred.'} + + +