import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'core/api/api_client_provider.dart'; import 'core/auth/auth_notifier.dart'; import 'core/chat/active_session_notifier.dart'; import 'core/chat/chat_notifier.dart'; import 'core/notifications/notification_service.dart'; import 'core/theme/halo_theme.dart'; import 'firebase_options.dart'; import 'router.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); final messaging = FirebaseMessaging.instance; await messaging.requestPermission(); runApp(const ProviderScope(child: App())); } class App extends ConsumerStatefulWidget { const App({super.key}); @override ConsumerState createState() => _AppState(); } class _AppState extends ConsumerState { bool _fcmRegistered = false; void _registerFcmToken() { if (_fcmRegistered) return; _fcmRegistered = true; Future(() async { try { final token = await FirebaseMessaging.instance.getToken(); if (token != null) { await ref.read(apiClientProvider).post('/api/shared/device-token', data: {'token': token}); } } catch (_) { _fcmRegistered = false; } }); } @override Widget build(BuildContext context) { // FCM registration on auth. ref.listen(authProvider, (prev, next) { final data = next.valueOrNull; if (data is AuthAuthenticatedData || data is AuthAnonymousData) { _registerFcmToken(); } else { // Logged out (or initial) — ensure the chat WS is closed. ref.read(chatProvider.notifier).disconnect(); } }); // Global chat WebSocket lifecycle: connect whenever the user has an // active session, regardless of which screen is mounted. The chat screen // only joins this connection — it doesn't own it. FCM remains the // background-only fallback. ref.listen(activeSessionProvider, (prev, next) { final snapshot = next.valueOrNull; final notifier = ref.read(chatProvider.notifier); if (snapshot == null || !snapshot.hasSession) { if (notifier.connectedSessionId != null) { notifier.disconnect(); } return; } final sessionId = snapshot.sessionId; if (sessionId != null && notifier.connectedSessionId != sessionId) { notifier.connectIfNotConnected(sessionId); } }); final router = ref.watch(routerProvider); NotificationService.initialize(router); return MaterialApp.router( title: 'Halo Bestie', theme: haloThemeData(), routerConfig: router, ); } }