import 'dart:async'; import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:fires_flutter/models/fireNotification.dart'; import 'package:fires_flutter/objectIdUtils.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 'activeFires.dart'; import 'colors.dart'; import 'fireAlert.dart'; import 'fireNotificationList.dart'; import 'firesSpinner.dart'; import 'generated/i18n.dart'; import 'mainDrawer.dart'; import 'models/appState.dart'; import 'redux/actions.dart'; class _ViewModel { final bool isLoaded; _ViewModel({required this.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 { static const String routeName = '/home'; HomePage(); @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { final GlobalKey _scaffoldKey = new 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 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(new OnUserTokenAction(token)); setState(() {}); } }); } final _homeFont = const TextStyle( fontSize: 50.0, fontWeight: FontWeight.w600, ); final _btnFont = const TextStyle( fontSize: 20.0, fontWeight: FontWeight.w600, ); @override Widget build(BuildContext context) { return new StoreConnector( distinct: true, converter: (store) { bool isLoaded = store.state.isLoaded; if (isLoaded && newNotifications.isNotEmpty) { newNotifications.forEach((notif) { store.dispatch(new AddFireNotificationAction(notif)); }); newNotifications.clear(); } return new _ViewModel(isLoaded: store.state.isLoaded); }, builder: (context, view) { return new Scaffold( key: _scaffoldKey, drawer: new 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: new Text(S.of(context).notifyAFire, style: _btnFont), icon: const Icon(Icons.notifications_active, size: 32.0), ), ), ]), body: !view.isLoaded ? new FiresSpinner() : new SafeArea( child: Center( child: new CenteredColumn(children: [ new Row( mainAxisAlignment: MainAxisAlignment.start, children: [ new IconButton( onPressed: () { _scaffoldKey.currentState?.openDrawer(); }, icon: new Icon(Icons.menu, size: 30.0, color: Colors.black38)), ]), new Expanded( child: new FractionallySizedBox( alignment: FractionalOffset.center, heightFactor: 0.7, child: new Image.asset('images/logo-200.png', fit: BoxFit.fitHeight))), new Expanded( child: new FractionallySizedBox( alignment: FractionalOffset.topCenter, heightFactor: 1.0, child: new Column( children: [ new Padding( padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 20.0), child: FittedBox( child: new Text(S.of(context).appName, maxLines: 2, textAlign: TextAlign.center, style: _homeFont), fit: BoxFit.scaleDown, )), ], ))) ])), )); }); } void _showDialog(String message) { final context = _scaffoldKey.currentContext; if (context == null) return; showDialog( context: context, builder: (_) => new AlertDialog( content: new Text(message), actions: [ new TextButton( child: Text(S.of(context).CLOSE), onPressed: () { Navigator.pop(context); }, ), ], )); } void _showItemDialog(Map message, FireNotification notif) { final context = _scaffoldKey.currentContext; if (context == null) return; showDialog( context: context, builder: (_) => _buildDialog(context, notif), ).then((bool? shouldNavigate) { if (shouldNavigate == true) { _navigateToItemDetail(message); } }).catchError((e) => print("$e")); } Widget _buildDialog(BuildContext context, FireNotification item) { return new AlertDialog( content: new Text(item.description), actions: [ new TextButton( child: Text(S.of(context).CLOSE), onPressed: () { Navigator.pop(context, false); }, ), new TextButton( child: Text(S.of(context).SHOW), onPressed: () { Navigator.pop(context, true); }, ), ], ); } void _navigateToItemDetail(Map message) { final 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 = new FireNotification( id: objectIdFromJson(message['id']), subsId: objectIdFromJson(message['subsId']), lat: double.parse(message['lat']), lon: double.parse(message['lon']), description: message['description'], read: false, when: DateTime.parse(message['when']), sealed: message['sealed']); 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(new AddFireNotificationAction(notif)); } else { newNotifications.add(notif); } } return notif; } }