import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:redux/redux.dart'; import 'customMoment.dart'; import 'firesSpinner.dart'; import 'generated/i18n.dart'; import 'genericMap.dart'; import 'mainDrawer.dart'; import 'models/appState.dart'; import 'models/fireNotification.dart'; import 'models/yourLocation.dart'; import 'redux/actions.dart'; @immutable class _ViewModel { const _ViewModel( {required this.isLoaded, required this.onTap, required this.onDelete, required this.onDeleteAll, required this.fireNotifications, required this.yourLocations, required this.fireNotificationsUnread}); final bool isLoaded; final List fireNotifications; final int fireNotificationsUnread; final List yourLocations; final TapFireNotificationFunction onTap; final DeleteFireNotificationFunction onDelete; final DeleteAllFireNotificationFunction onDeleteAll; @override bool operator ==(Object other) => identical(this, other) || other is _ViewModel && runtimeType == other.runtimeType && isLoaded == other.isLoaded && fireNotifications == other.fireNotifications && fireNotificationsUnread == other.fireNotificationsUnread && yourLocations == other.yourLocations; @override int get hashCode => isLoaded.hashCode ^ fireNotifications.hashCode ^ fireNotificationsUnread.hashCode ^ yourLocations.hashCode; } class FireNotificationList extends StatefulWidget { const FireNotificationList({super.key}); static const String routeName = '/fireNotifications'; @override _FireNotificationListState createState() => _FireNotificationListState(); } class _FireNotificationListState extends State { final GlobalKey _scaffoldKey = GlobalKey(); Widget _buildRow( BuildContext context, List yourLocations, FireNotification notif, DeleteFireNotificationFunction onDeleted, TapFireNotificationFunction onTap) { String prefix = ''; // FIXME (this can fails if you don't have a location for this notif, for instance during tests) final YourLocation yl = yourLocations.singleWhere((YourLocation yl) => yl.id == notif.subsId); prefix = '${yl.description}. '; return ListTile( dense: true, leading: const Icon(Icons.whatshot), title: Text('$prefix${notif.description}', style: TextStyle( fontWeight: notif.read ? FontWeight.normal : FontWeight.bold)), subtitle: Text(Moment.now().from(context, notif.when)), onLongPress: () { showSnackMsg(S.of(context).toDeleteThisNotification); }, onTap: () { onTap(notif); }); } void showSnackMsg(String msg) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(msg), )); } Widget _buildSavedFireNotifications( BuildContext context, List yourLocations, List notifList, DeleteFireNotificationFunction onDeleted, TapFireNotificationFunction onTap) { return RefreshIndicator( onRefresh: _handleRefresh, child: ListView.builder( padding: const EdgeInsets.all(16.0), // reverse: true, // shrinkWrap: true, itemCount: notifList.length, itemBuilder: (BuildContext context, int i) { final ThemeData theme = Theme.of(context); return Dismissible( key: ObjectKey(notifList.elementAt(i)), onDismissed: (DismissDirection direction) { onDeleted(notifList.elementAt(i)); }, background: Container( color: theme.primaryColor, child: const ListTile( leading: Icon(Icons.delete, color: Colors.white, size: 36.0))), secondaryBackground: Container( color: theme.primaryColor, child: const ListTile( trailing: Icon(Icons.delete, color: Colors.white, size: 36.0))), child: Container( decoration: BoxDecoration( color: theme.canvasColor, border: Border( bottom: BorderSide(color: theme.dividerColor))), child: _buildRow(context, yourLocations, notifList.elementAt(i), onDeleted, onTap))); })); } Future _handleRefresh() async { await Future.delayed(const Duration(seconds: 1)); setState(() {}); return; } @override Widget build(BuildContext context) { return StoreConnector( distinct: true, converter: (Store store) { return _ViewModel( isLoaded: store.state.isLoaded, onDeleteAll: () { store.dispatch(DeleteAllFireNotificationAction()); }, onDelete: (FireNotification notif) { store.dispatch(DeleteFireNotificationAction(notif)); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(S.of(context).youDeletedThisNotification), action: SnackBarAction( label: S.of(context).UNDO, onPressed: () { store.dispatch(AddFireNotificationAction(notif)); }))); }, onTap: (FireNotification notif) { if (!notif.read) { store.dispatch( ReadFireNotificationAction(notif.copyWith(read: true))); } Timer(const Duration(milliseconds: 500), () { gotoMap(store, notif, context); }); }, yourLocations: store.state.yourLocations, fireNotifications: store.state.fireNotifications, fireNotificationsUnread: store.state.fireNotificationsUnread); }, builder: (BuildContext context, _ViewModel view) { final bool hasFireNotifications = view.fireNotifications.isNotEmpty; final String title = S.of(context).fireNotificationsTitle; return Scaffold( key: _scaffoldKey, drawer: MainDrawer(context, FireNotificationList.routeName), appBar: AppBar( title: Text(title), leading: IconButton( icon: const Icon(Icons.menu), onPressed: () { _scaffoldKey.currentState?.openDrawer(); }, ), actions: [ if (hasFireNotifications) IconButton( icon: const Icon(Icons.delete), onPressed: () => _showConfirmDialog(view)) else null ].where((Widget? w) => w != null).cast().toList()), body: !view.isLoaded ? const FiresSpinner() : !hasFireNotifications ? Padding( padding: const EdgeInsets.all(20.0), child: Card( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.notifications_none, size: 150.0, color: Colors.black26), const SizedBox(height: 20.0), Text( S .of(context) .fireNotificationsDescription, textAlign: TextAlign.center, textScaler: const TextScaler.linear(1.1), style: const TextStyle( height: 1.3, color: Colors.black45)) ])))) : _buildSavedFireNotifications( context, view.yourLocations, view.fireNotifications, view.onDelete, view.onTap)); }); } void gotoMap( Store store, FireNotification notif, BuildContext context) { store.dispatch(ShowFireNotificationMapAction(notif)); Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => const GenericMap())); } Future _showConfirmDialog(_ViewModel view) { return showDialog( context: context, barrierDismissible: false, // user must tap button! builder: (BuildContext context) { return AlertDialog( title: Text(S.of(context).areYouSureTitle), content: SingleChildScrollView( child: ListBody( children: [ Text(S.of(context).deleteAllFireNotificationsAlertDescription) ], ), ), actions: [ TextButton( child: Text(S.of(context).DELETE), onPressed: () { view.onDeleteAll(); Navigator.of(context).pop(); }, ), ], ); }, ); } }