import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'auth_service.dart'; import 'chat_backup_manager.dart'; import 'secure_chat_service.dart'; import 'simple_e2ee_service.dart'; import 'local_message_store.dart'; class SyncManager with WidgetsBindingObserver { final SecureChatService _secureChatService; final AuthService _authService; final SimpleE2EEService _e2ee = SimpleE2EEService(); final LocalMessageStore _localStore = LocalMessageStore.instance; final Duration _resumeThreshold; final Duration _syncInterval; Timer? _timer; StreamSubscription? _authSub; DateTime? _lastSyncAt; bool _syncInProgress = false; bool _initialized = false; bool _hydrating = false; SyncManager({ required SecureChatService secureChatService, required AuthService authService, Duration resumeThreshold = const Duration(minutes: 2), Duration syncInterval = const Duration(minutes: 5), }) : _secureChatService = secureChatService, _authService = authService, _resumeThreshold = resumeThreshold, _syncInterval = syncInterval; void init() { if (_initialized) return; _initialized = true; WidgetsBinding.instance.addObserver(this); _authSub = _authService.authStateChanges.listen(_handleAuthChange); if (_authService.isAuthenticated) { _startTimer(); // Defer heavy sync work so the UI thread isn't blocked Future.delayed(const Duration(milliseconds: 500), () { if (!_initialized) return; if (kDebugMode) debugPrint('[SyncManager] Deferred startup sync starting'); // On web, skip eager chat sync — Hive/IndexedDB is too slow to open // at startup (~130s). Chat loads lazily when user opens a conversation. if (!kIsWeb) { unawaited(ensureHistoryLoaded()); _triggerSync(reason: 'startup'); } }); } } void dispose() { if (!_initialized) return; _initialized = false; WidgetsBinding.instance.removeObserver(this); _authSub?.cancel(); _authSub = null; _stopTimer(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { if (_authService.isAuthenticated) { _startTimer(); _syncIfStale(); } } else if (state == AppLifecycleState.paused) { _stopTimer(); // Trigger backup when app goes to background ChatBackupManager.instance.triggerBackupIfNeeded(); } else if (state == AppLifecycleState.inactive || state == AppLifecycleState.detached) { _stopTimer(); } } void _handleAuthChange(AuthState state) { if (state.event == AuthChangeEvent.signedIn || state.event == AuthChangeEvent.tokenRefreshed) { _startTimer(); if (!kIsWeb) { unawaited(ensureHistoryLoaded()); _triggerSync(reason: 'auth'); } } else if (state.event == AuthChangeEvent.signedOut) { _lastSyncAt = null; _stopTimer(); } } void _startTimer() { _timer?.cancel(); _timer = Timer.periodic(_syncInterval, (_) { if (_authService.isAuthenticated && !kIsWeb) { _triggerSync(reason: 'timer'); } }); } void _stopTimer() { _timer?.cancel(); _timer = null; } void _syncIfStale() { if (_lastSyncAt == null || DateTime.now().difference(_lastSyncAt!) > _resumeThreshold) { _triggerSync(reason: 'resume'); } } Future _triggerSync({required String reason}) async { if (!_authService.isAuthenticated || _syncInProgress) return; _syncInProgress = true; try { await _e2ee.initialize(); if (!_e2ee.isReady) { return; } // On web, skip eager hydration — Hive/IndexedDB startup is too slow. if (!kIsWeb) { await ensureHistoryLoaded(); } await _secureChatService.syncAllConversations(force: true); _lastSyncAt = DateTime.now(); // Schedule auto-backup after successful sync ChatBackupManager.instance.scheduleBackup(); } catch (e) { } finally { _syncInProgress = false; } } Future ensureHistoryLoaded() async { if (!_authService.isAuthenticated || _hydrating) return; _hydrating = true; try { await _e2ee.initialize(); if (!_e2ee.isReady) { return; } final conversations = await _secureChatService.getConversations(); for (final conv in conversations) { final isEmpty = (await _localStore.getMessageIdsForConversation(conv.id)).isEmpty; if (isEmpty) { await _secureChatService.fetchAndDecryptHistory(conv.id, limit: 50); } await _secureChatService.startLiveListener(conv.id); } } catch (e) { } finally { _hydrating = false; } } }