import 'dart:async'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:get_it/get_it.dart'; import 'package:redux/redux.dart'; import 'active_fires.dart'; import 'colors.dart'; import 'fire_alert.dart'; import 'fire_notification_list.dart'; import 'fires_spinner.dart'; import 'generated/i18n.dart'; import 'main_drawer.dart'; import 'models/app_state.dart'; import 'models/fire_notification.dart'; import 'object_id_utils.dart'; import 'redux/actions.dart'; @immutable class _ViewModel { const _ViewModel({required this.isLoaded}); final bool isLoaded; @override bool operator ==(Object other) => identical(this, other) || other is _ViewModel && runtimeType == other.runtimeType && isLoaded == other.isLoaded; @override int get hashCode => isLoaded.hashCode; } class HomePage extends StatefulWidget { const HomePage({super.key}); static const String routeName = '/home'; @override // ignore: library_private_types_in_public_api _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { final GlobalKey _scaffoldKey = GlobalKey(); final Store store = GetIt.instance>(); final List newNotifications = []; // Platform messages are asynchronous, so we initialize in an async method. Future initConnectivity() async { // Connectivity checking removed - no longer needed } @override void initState() { super.initState(); // Firebase Messaging v5+ setup _setupFirebaseMessaging(); _getFirebaseToken(); // initConnectivity removed - connectivity no longer tracked } void _setupFirebaseMessaging() { // Request permission for notifications _firebaseMessaging.requestPermission(); // Listen for messages when app is in foreground FirebaseMessaging.onMessage.listen((RemoteMessage message) { debugPrint( 'onMessage in fireApp (isLoaded: ${store.state.isLoaded}): $message'); if (message.data.isNotEmpty) { final FireNotification? notif = _notifForMessage(message.data, store.state.isLoaded); if (notif != null) { _showItemDialog(message.data, notif); } } }); // Listen for messages when app is opened from background FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { debugPrint( 'onMessageOpenedApp (isLoaded: ${store.state.isLoaded}): $message'); if (message.data.isNotEmpty) { _notifForMessage(message.data, store.state.isLoaded); _navigateToItemDetail(message.data); } }); // Check if app was terminated and opened by notification tap _firebaseMessaging.getInitialMessage().then((RemoteMessage? message) { if (message != null) { debugPrint('App opened by notification: $message'); if (message.data.isNotEmpty) { _navigateToItemDetail(message.data); } } }); } void _getFirebaseToken() { _firebaseMessaging.getToken().then((String? token) { if (token != null) { store.dispatch(OnUserTokenAction(token)); setState(() {}); } }); } final TextStyle _homeFont = const TextStyle( fontSize: 50.0, fontWeight: FontWeight.w600, ); final TextStyle _btnFont = const TextStyle( fontSize: 20.0, fontWeight: FontWeight.w600, ); @override Widget build(BuildContext context) { return StoreConnector( distinct: true, converter: (Store store) { final bool isLoaded = store.state.isLoaded; if (isLoaded && newNotifications.isNotEmpty) { for (final FireNotification notif in newNotifications) { store.dispatch(AddFireNotificationAction(notif)); } newNotifications.clear(); } return _ViewModel(isLoaded: store.state.isLoaded); }, builder: (BuildContext context, _ViewModel view) { return Scaffold( key: _scaffoldKey, drawer: MainDrawer(context, HomePage.routeName), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton.extended( elevation: 0.0, onPressed: () { Navigator.pushNamed(context, ActiveFiresPage.routeName); }, label: Text(S.of(context).activeFires, style: _btnFont), backgroundColor: fires600, heroTag: 'activeFires', icon: const Icon(Icons.whatshot, size: 32.0), ), Padding( padding: const EdgeInsets.only(top: 16.0, bottom: 26.0), child: FloatingActionButton.extended( elevation: 0.0, onPressed: () { Navigator.pushNamed(context, FireAlert.routeName); }, heroTag: 'notifyFire', backgroundColor: fires600, label: Text(S.of(context).notifyAFire, style: _btnFont), icon: const Icon(Icons.notifications_active, size: 32.0), ), ), ]), body: !view.isLoaded ? const FiresSpinner() : SafeArea( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row(children: [ IconButton( onPressed: () { _scaffoldKey.currentState?.openDrawer(); }, icon: const Icon(Icons.menu, size: 30.0, color: Colors.black38)), ]), Expanded( child: FractionallySizedBox( alignment: FractionalOffset.center, heightFactor: 0.7, child: Image.asset('images/logo-200.png', fit: BoxFit.fitHeight))), Expanded( child: FractionallySizedBox( alignment: FractionalOffset.topCenter, heightFactor: 1.0, child: Column( children: [ Padding( padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 20.0), child: FittedBox( fit: BoxFit.scaleDown, child: Text(S.of(context).appName, maxLines: 2, textAlign: TextAlign.center, style: _homeFont), )), ], ))) ])), )); }); } void _showItemDialog(Map message, FireNotification notif) { final BuildContext? context = _scaffoldKey.currentContext; if (context == null) { return; } showDialog( context: context, builder: (_) => _buildDialog(context, notif), ).then((bool? shouldNavigate) { if (shouldNavigate ?? false) { _navigateToItemDetail(message); } }).catchError((Object e) {}); } Widget _buildDialog(BuildContext context, FireNotification item) { return AlertDialog( content: Text(item.description), actions: [ TextButton( child: Text(S.of(context).CLOSE), onPressed: () { Navigator.pop(context, false); }, ), TextButton( child: Text(S.of(context).SHOW), onPressed: () { Navigator.pop(context, true); }, ), ], ); } void _navigateToItemDetail(Map message) { final BuildContext? context = _scaffoldKey.currentContext; if (context == null) { return; } // Clear away dialogs Navigator.popUntil(context, (Route route) => route is PageRoute); /* if (!notif.getRoute(store).isCurrent) { // Navigator.push(context, notif.getRoute(store)); } */ Navigator.pushNamed(context, FireNotificationList.routeName); } // Firebase Messaging instance (v5+) final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; FireNotification? _notifForMessage( Map message, bool isLoaded) { FireNotification? notif; try { notif = FireNotification( id: objectIdFromJson(message['id'] as String), subsId: objectIdFromJson(message['subsId'] as String), lat: double.parse(message['lat'] as String), lon: double.parse(message['lon'] as String), description: message['description'] as String, read: false, when: DateTime.parse(message['when'] as String), sealed: message['sealed'] as String); debugPrint(notif.toString()); } catch (e) { debugPrint(e.toString()); } // if our store is loaded, we just dispatch the notification, if not, we wait til is loaded if (notif != null) { if (isLoaded) { store.dispatch(AddFireNotificationAction(notif)); } else { newNotifications.add(notif); } } return notif; } }