import 'dart:async'; import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:redux/redux.dart'; import 'colors.dart'; import 'firesSpinner.dart'; import 'generated/i18n.dart'; import 'genericMap.dart'; import 'globalFiresBottomStats.dart'; import 'locationUtils.dart'; import 'mainDrawer.dart'; import 'models/appState.dart'; import 'models/yourLocation.dart'; import 'placesAutocompleteUtils.dart'; import 'redux/actions.dart'; @immutable class _ViewModel { const _ViewModel( {required this.onAdd, required this.onDelete, required this.onToggleSubs, required this.onTap, required this.onRefresh, required this.yourLocations, required this.isLoading}); final List yourLocations; final AddYourLocationFunction onAdd; final DeleteYourLocationFunction onDelete; final ToggleSubscriptionFunction onToggleSubs; final OnLocationTapFunction onTap; final OnRefreshYourLocationsFunction onRefresh; final bool isLoading; @override bool operator ==(Object other) => identical(this, other) || other is _ViewModel && runtimeType == other.runtimeType && yourLocations == other.yourLocations && isLoading == other.isLoading; @override int get hashCode => yourLocations.hashCode ^ isLoading.hashCode; } class ActiveFiresPage extends StatefulWidget { const ActiveFiresPage({super.key}); static const String routeName = '/fires'; @override _ActiveFiresPageState createState() => _ActiveFiresPageState(); } class _ActiveFiresPageState extends State { final GlobalKey _scaffoldKey = GlobalKey(); List _fabMiniMenuItemList( BuildContext context, AddYourLocationFunction onAdd) { return [ SpeedDialChild( child: const Icon(Icons.location_searching), backgroundColor: fires600, label: S.of(context).addYourCurrentPosition, labelWidget: Padding( padding: const EdgeInsets.only(right: 16.0), child: Container( color: Colors.white, child: Text(S.of(context).addYourCurrentPosition, style: const TextStyle(color: Colors.black38)), ), ), onTap: () { onAddYourLocation(onAdd); }, ), SpeedDialChild( child: const Icon(Icons.edit_location), backgroundColor: fires600, label: S.of(context).addSomePlace, labelWidget: Padding( padding: const EdgeInsets.only(right: 16.0), child: Container( color: Colors.white, child: Text(S.of(context).addSomePlace, style: const TextStyle(color: Colors.black38)), ), ), onTap: () { onAddOtherLocation(onAdd); }, ) ]; } Widget _buildRow(BuildContext context, YourLocation loc, ToggleSubscriptionFunction onToggle, OnLocationTapFunction onTap) { const TextStyle fireStatsStyle = TextStyle(color: fires600); return ListTile( dense: true, isThreeLine: false, // leading: const Icon(Icons.location_on), trailing: IconButton( icon: Icon(loc.subscribed ? Icons.notifications_active : Icons.notifications_off), color: loc.subscribed ? fires600 : null, onPressed: () { loc.subscribed = !loc.subscribed; onToggle(loc); setState(() {}); showSnackMsg(loc.subscribed ? S.of(context).subscribedToFires : S.of(context).unsubscribedToFires); }), title: Text(loc.description, style: const TextStyle(fontSize: 16.0)), subtitle: loc.currentNumFires == YourLocation.withoutStats ? null : loc.currentNumFires == 0 ? Text(S.of(context).noFiresAround) : loc.currentNumFires == 1 ? Text( S .of(context) .fireAroundThisArea(loc.distance.toString()), style: fireStatsStyle) : Text( S.of(context).firesAroundThisArea( loc.currentNumFires.toString(), loc.distance.toString()), style: fireStatsStyle), onLongPress: () { showSnackMsg(S.of(context).toDeleteThisPlace); }, onTap: () { onTap(loc); }); } void showSnackMsg(String msg) { // _scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(msg), )); } Widget _buildSavedLocations( BuildContext context, List yl, DeleteYourLocationFunction onDeleted, ToggleSubscriptionFunction onToggle, OnLocationTapFunction onTap) { return ListView.builder( padding: const EdgeInsets.all(16.0), itemCount: yl.length, itemBuilder: (BuildContext context, int i) { final ThemeData theme = Theme.of(context); return Dismissible( key: ObjectKey(yl.elementAt(i)), onDismissed: (DismissDirection direction) { onDeleted(yl.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, yl.elementAt(i), onToggle, onTap))); }); } @override Widget build(BuildContext context) { return StoreConnector( distinct: true, converter: (Store store) { print('New ViewModel of Active Fires'); return _ViewModel( onAdd: (YourLocation loc) { if (store.state.yourLocations.any( (YourLocation l) => loc.lat == l.lat && loc.lon == l.lon)) { // Already added showSnackMsg(S.of(context).addedThisLocation); } else { store.dispatch(AddYourLocationAction(loc)); Timer(const Duration(milliseconds: 1000), () { gotoLocationMap(store, loc, context); }); } }, onDelete: (YourLocation loc) { store.dispatch(DeleteYourLocationAction(loc)); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(S.of(context).youDeletedThisPlace), action: SnackBarAction( label: S.of(context).UNDO, onPressed: () { store.dispatch(AddYourLocationAction(loc)); }))); }, onToggleSubs: (YourLocation loc) { store.dispatch(ToggleSubscriptionAction(loc)); }, onTap: (YourLocation loc) { gotoLocationMap(store, loc, context); }, onRefresh: (Completer refreshCallback) => store.dispatch(FetchYourLocationsAction(refreshCallback)), yourLocations: store.state.yourLocations, isLoading: !store.state.isLoaded); }, builder: (BuildContext context, _ViewModel view) { final bool hasLocations = view.yourLocations.isNotEmpty; final String title = hasLocations ? S.of(context).firesInYourPlaces : S.of(context).firesInTheWorld; print('Building Active Fires'); return Scaffold( key: _scaffoldKey, // FIXME new? drawer: MainDrawer(context, ActiveFiresPage.routeName), appBar: AppBar( title: Text(title), leading: IconButton( icon: const Icon(Icons.menu), onPressed: () { _scaffoldKey.currentState?.openDrawer(); }, ), ), floatingActionButton: SpeedDial( icon: Icons.add, activeIcon: Icons.close, backgroundColor: fires600, spacing: 12, children: _fabMiniMenuItemList(context, view.onAdd), ), bottomNavigationBar: const GlobalFiresBottomStats(), body: view.isLoading ? const FiresSpinner() : hasLocations ? RefreshIndicator( child: _buildSavedLocations(context, view.yourLocations, view.onDelete, view.onToggleSubs, view.onTap), onRefresh: () async { final Completer completer = Completer(); view.onRefresh(completer); return completer.future; }) : Center( child: CenteredColumn(children: [ RoundedBtn( icon: Icons.location_searching, text: S.of(context).addYourCurrentPosition, onPressed: () => onAddYourLocation(view.onAdd), backColor: fires600), const SizedBox(height: 26.0), RoundedBtn( icon: Icons.edit_location, text: S.of(context).addSomePlace, onPressed: () => onAddOtherLocation(view.onAdd), backColor: fires600), ])), ); }); } void gotoLocationMap( Store store, YourLocation loc, BuildContext context) { store.dispatch(ShowYourLocationMapAction(loc)); Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => genericMap())); } void onAddYourLocation(AddYourLocationFunction onAdd) { final Future location = getUserLocation(_scaffoldKey); _saveLocation(location, onAdd); } void onAddOtherLocation(AddYourLocationFunction onAdd) { final Future location = openPlacesDialog(_scaffoldKey); _saveLocation(location, onAdd); } void _saveLocation( Future location, AddYourLocationFunction onAdd) { location.then((YourLocation newLocation) { if (newLocation != YourLocation.noLocation) { onAdd(newLocation); } }); } }