mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 16:59:41 +02:00
Projects included: - maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing) - manacore (Expo mobile + SvelteKit web + Astro landing) - manadeck (NestJS backend + Expo mobile + SvelteKit web) - memoro (Expo mobile + SvelteKit web + Astro landing) This commit preserves the current state before monorepo restructuring. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
152 lines
No EOL
5.3 KiB
TypeScript
152 lines
No EOL
5.3 KiB
TypeScript
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
import NetInfo, { NetInfoState } from '@react-native-community/netinfo';
|
|
import { View, Text, StyleSheet } from 'react-native';
|
|
import { useTheme } from '~/features/theme/ThemeProvider';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
interface NetworkStatusContextType {
|
|
isConnected: boolean;
|
|
isInternetReachable: boolean | null;
|
|
networkType: string;
|
|
details: NetInfoState | null;
|
|
}
|
|
|
|
const NetworkStatusContext = createContext<NetworkStatusContextType>({
|
|
isConnected: true,
|
|
isInternetReachable: true,
|
|
networkType: 'unknown',
|
|
details: null,
|
|
});
|
|
|
|
export const useNetworkStatus = () => useContext(NetworkStatusContext);
|
|
|
|
export const NetworkStatusProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [networkState, setNetworkState] = useState<NetInfoState | null>(null);
|
|
const [previousState, setPreviousState] = useState<NetInfoState | null>(null);
|
|
const { isDark } = useTheme();
|
|
const { t } = useTranslation();
|
|
|
|
useEffect(() => {
|
|
let reconnectionTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
// Subscribe to network state updates
|
|
const unsubscribe = NetInfo.addEventListener(state => {
|
|
console.debug('Network state changed:', {
|
|
isConnected: state.isConnected,
|
|
isInternetReachable: state.isInternetReachable,
|
|
type: state.type,
|
|
});
|
|
|
|
// Detect transition from offline to online
|
|
if (previousState &&
|
|
(!previousState.isConnected || !previousState.isInternetReachable) &&
|
|
state.isConnected && state.isInternetReachable) {
|
|
|
|
console.log('🌐 Network reconnected: Offline -> Online transition detected');
|
|
|
|
// Clear any existing timeout
|
|
if (reconnectionTimeout) {
|
|
clearTimeout(reconnectionTimeout);
|
|
}
|
|
|
|
// Wait for connection to stabilize before triggering reconnection logic
|
|
reconnectionTimeout = setTimeout(async () => {
|
|
console.log('🌐 Network stabilized, triggering reconnection handlers');
|
|
|
|
// Import tokenManager and TokenState dynamically to avoid circular dependencies
|
|
try {
|
|
const { tokenManager, TokenState } = await import('~/features/auth/services/tokenManager');
|
|
const tokenState = tokenManager.getState();
|
|
console.log('Current token state after reconnection:', tokenState);
|
|
|
|
// If token expired while offline, attempt refresh now
|
|
if (tokenState === TokenState.EXPIRED_OFFLINE) {
|
|
console.log('🔄 Token expired offline, attempting refresh after reconnection');
|
|
const refreshed = await tokenManager.retryRefreshIfNeeded();
|
|
|
|
if (refreshed) {
|
|
console.log('✅ Token refreshed successfully after reconnection');
|
|
} else {
|
|
console.warn('⚠️ Token refresh failed after reconnection');
|
|
}
|
|
} else {
|
|
console.log('Token state is:', tokenState, '- no refresh needed');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking/refreshing token after reconnection:', error);
|
|
}
|
|
|
|
// Trigger realtime reconnection for memos
|
|
try {
|
|
const { default: memoRealtimeService } = await import('~/features/memos/services/memoRealtimeService');
|
|
const status = memoRealtimeService.getStatus();
|
|
if (status.isInitialized && !status.hasActiveSubscription) {
|
|
console.log('🔄 Reconnecting memo realtime subscriptions');
|
|
await memoRealtimeService.initialize();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error reconnecting memo realtime:', error);
|
|
}
|
|
|
|
// Note: Automatic upload retry removed - users must manually retry uploads
|
|
// Manual upload functionality remains available through the UI
|
|
|
|
}, 2000); // 2 second delay for connection stability
|
|
}
|
|
|
|
setPreviousState(networkState);
|
|
setNetworkState(state);
|
|
});
|
|
|
|
// Get initial state
|
|
NetInfo.fetch().then(state => {
|
|
setNetworkState(state);
|
|
});
|
|
|
|
return () => {
|
|
unsubscribe();
|
|
if (reconnectionTimeout) {
|
|
clearTimeout(reconnectionTimeout);
|
|
}
|
|
};
|
|
}, [networkState]);
|
|
|
|
const value: NetworkStatusContextType = {
|
|
isConnected: networkState?.isConnected ?? true,
|
|
isInternetReachable: networkState?.isInternetReachable ?? null,
|
|
networkType: networkState?.type ?? 'unknown',
|
|
details: networkState,
|
|
};
|
|
|
|
return (
|
|
<NetworkStatusContext.Provider value={value}>
|
|
{children}
|
|
{/* Offline banner */}
|
|
{networkState && !networkState.isConnected && (
|
|
<View style={[styles.offlineBanner, { backgroundColor: isDark ? '#ff6b6b' : '#e74c3c' }]}>
|
|
<Text style={styles.offlineText}>
|
|
{t('common.offline', 'No internet connection')}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</NetworkStatusContext.Provider>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
offlineBanner: {
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
paddingVertical: 8,
|
|
paddingHorizontal: 16,
|
|
zIndex: 9999,
|
|
},
|
|
offlineText: {
|
|
color: '#ffffff',
|
|
textAlign: 'center',
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
},
|
|
}); |