From 664e1dd29089946314846ffa106606aa3c02f4b2 Mon Sep 17 00:00:00 2001 From: m Date: Wed, 26 Aug 2026 15:35:42 +0200 Subject: [PATCH] add status --- mobile/app/index.tsx | 2 + mobile/components/NetworkStatusBar.tsx | 54 +++++++++++++++++++++++++ mobile/hooks/useNetworkStatus.ts | 55 ++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 mobile/components/NetworkStatusBar.tsx create mode 100644 mobile/hooks/useNetworkStatus.ts diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx index 2279aec..75a55e8 100644 --- a/mobile/app/index.tsx +++ b/mobile/app/index.tsx @@ -15,6 +15,7 @@ import { SettingsModal } from '../components/SettingsModal'; import { ConfirmModal, type ConfirmOption } from '../components/ConfirmModal'; import { UploadModal } from '../components/UploadModal'; import { SyncStatusIcon } from '../components/SyncStatusIcon'; +import { NetworkStatusBar } from '../components/NetworkStatusBar'; import { useSyncQueue } from '../hooks/useSyncQueue'; import { useUploadQueue } from '../hooks/useUploadQueue'; import { useAutoSync } from '../hooks/useAutoSync'; @@ -161,6 +162,7 @@ export function HomeScreen() { navigation.setOptions({ headerRight: () => ( + + + + ); + } + + return ( + + + + Offline + + + ); +} + +const styles = StyleSheet.create({ + container: { + marginRight: 4, + padding: 4, + alignItems: 'center', + justifyContent: 'center', + }, + dotOnline: { + width: 8, + height: 8, + borderRadius: 4, + backgroundColor: '#4CAF50', + }, + offlineBadge: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#E53935', + borderRadius: 12, + paddingHorizontal: 8, + paddingVertical: 4, + gap: 4, + }, + offlineText: { + fontSize: 11, + fontWeight: '700', + color: '#fff', + }, +}); diff --git a/mobile/hooks/useNetworkStatus.ts b/mobile/hooks/useNetworkStatus.ts new file mode 100644 index 0000000..9a23b65 --- /dev/null +++ b/mobile/hooks/useNetworkStatus.ts @@ -0,0 +1,55 @@ +import { useSyncExternalStore, useRef } from 'react'; +import NetInfo, { NetInfoState, NetInfoSubscription } from '@react-native-community/netinfo'; + +type Listener = () => void; + +let state: NetInfoState | null = null; +const listeners = new Set(); +let subscription: NetInfoSubscription | null = null; + +function subscribe(listener: Listener): () => void { + listeners.add(listener); + return () => { listeners.delete(listener); }; +} + +function getSnapshot(): boolean { + return state?.isConnected ?? true; +} + +function initIfNeeded() { + if (subscription) return; + NetInfo.fetch().then((info) => { + state = info; + listeners.forEach((l) => l()); + }); + subscription = NetInfo.addEventListener((info) => { + state = info; + listeners.forEach((l) => l()); + }); +} + +export interface NetworkStatus { + isOnline: boolean; + isWifi: boolean; + isCellular: boolean; + connectionType: string; + isInternetReachable: boolean | null; +} + +export function useNetworkStatus(): NetworkStatus { + const mountRef = useRef(false); + if (!mountRef.current) { + initIfNeeded(); + mountRef.current = true; + } + + const isConnected = useSyncExternalStore(subscribe, getSnapshot); + + return { + isOnline: isConnected, + isWifi: state?.type === 'wifi', + isCellular: state?.type === 'cellular', + connectionType: state?.type ?? 'unknown', + isInternetReachable: state?.isInternetReachable ?? null, + }; +}