todos-contra-el-fuego-mobile/lib/activeFires.dart
vjrj a7f3ab6974 Fix remaining compilation errors: null-safety, removed packages, and nullable handling
- Fix AppState: remove removed connectivity package, fix imports (latlong2), make nullable fields optional
- Fix User model: use const empty strings in const constructor instead of null
- Fix model builders: add required ObjectId parameters to YourLocation and FireNotification
- Implement Comparable instead of extending it for BasicLocation
- Fix nullable callback handling in appActions and middleware (Completer<Null>?)
- Add null checks for BuildContext in dialogs (fireAlert, homePage, etc.)
- Fix nullable scaffold state access using ?. operator (activeFires, fireNotificationList)
- Handle nullable FireNotification and YourLocation in genericMapBottom
- Fix list type casting for widgets that can be null (<Widget?> with filtering)
- Add ObjectId imports where needed for model creation
- Fix Key? nullable parameter in introPage
- Add required parameters to markdownPage and slider constructors
- Remove connectivity-related code and imports (package removed)
- Handle nullable Completer in fetchDataMiddleware

All 104 errors resolved. 0 errors, 61 warnings/info remaining.
2026-03-05 02:50:32 +01:00

305 lines
11 KiB
Dart

import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/yourLocation.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 'placesAutocompleteUtils.dart';
import 'redux/actions.dart';
@immutable
class _ViewModel {
final List<YourLocation> yourLocations;
final AddYourLocationFunction onAdd;
final DeleteYourLocationFunction onDelete;
final ToggleSubscriptionFunction onToggleSubs;
final OnLocationTapFunction onTap;
final OnRefreshYourLocationsFunction onRefresh;
final bool isLoading;
_ViewModel(
{required this.onAdd,
required this.onDelete,
required this.onToggleSubs,
required this.onTap,
required this.onRefresh,
required this.yourLocations,
required this.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 {
static const String routeName = '/fires';
@override
_ActiveFiresPageState createState() => _ActiveFiresPageState();
}
class _ActiveFiresPageState extends State<ActiveFiresPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
List<SpeedDialChild> _fabMiniMenuItemList(
BuildContext context, AddYourLocationFunction onAdd) {
return [
SpeedDialChild(
child: const Icon(Icons.location_searching),
backgroundColor: fires600,
label: S.of(context).addYourCurrentPosition,
labelWidget: 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: 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, onToggle, onTap) {
final fireStatsStyle = new TextStyle(color: fires600);
return new ListTile(
dense: true,
isThreeLine: false,
// leading: const Icon(Icons.location_on),
trailing: new IconButton(
icon: new 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: new Text(loc.description, style: new TextStyle(fontSize: 16.0)),
subtitle: loc.currentNumFires == YourLocation.withoutStats
? null
: loc.currentNumFires == 0
? new Text(S.of(context).noFiresAround)
: loc.currentNumFires == 1
? new Text(
S
.of(context)
.fireAroundThisArea(loc.distance.toString()),
style: fireStatsStyle)
: new 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(new SnackBar(
content: new Text(msg),
));
}
Widget _buildSavedLocations(
BuildContext context, List<YourLocation> yl, onDeleted, onToggle, onTap) {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: yl.length,
itemBuilder: (BuildContext _context, int i) {
final ThemeData theme = Theme.of(context);
return new Dismissible(
key: new ObjectKey(yl.elementAt(i)),
direction: DismissDirection.horizontal,
onDismissed: (DismissDirection direction) {
onDeleted(yl.elementAt(i));
},
background: new Container(
color: theme.primaryColor,
child: const ListTile(
leading: const Icon(Icons.delete,
color: Colors.white, size: 36.0))),
secondaryBackground: new Container(
color: theme.primaryColor,
child: const ListTile(
trailing: const Icon(Icons.delete,
color: Colors.white, size: 36.0))),
child: new Container(
decoration: new BoxDecoration(
color: theme.canvasColor,
border: new Border(
bottom: new BorderSide(color: theme.dividerColor))),
child: _buildRow(context, yl.elementAt(i), onToggle, onTap)));
});
}
@override
Widget build(BuildContext context) {
return new StoreConnector<AppState, _ViewModel>(
distinct: true,
converter: (store) {
print('New ViewModel of Active Fires');
return new _ViewModel(
onAdd: (loc) {
if (store.state.yourLocations
.any((l) => loc.lat == l.lat && loc.lon == l.lon)) {
// Already added
showSnackMsg(S.of(context).addedThisLocation);
} else {
store.dispatch(new AddYourLocationAction(loc));
new Timer(new Duration(milliseconds: 1000), () {
gotoLocationMap(store, loc, context);
});
}
},
onDelete: (loc) {
store.dispatch(new DeleteYourLocationAction(loc));
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).youDeletedThisPlace),
action: new SnackBarAction(
label: S.of(context).UNDO,
onPressed: () {
store.dispatch(new AddYourLocationAction(loc));
})));
},
onToggleSubs: (loc) {
store.dispatch(new ToggleSubscriptionAction(loc));
},
onTap: (loc) {
gotoLocationMap(store, loc, context);
},
onRefresh: (refreshCallback) =>
store.dispatch(new FetchYourLocationsAction(refreshCallback)),
yourLocations: store.state.yourLocations,
isLoading: !store.state.isLoaded);
},
builder: (context, view) {
var hasLocations = view.yourLocations.length > 0;
final title = hasLocations
? S.of(context).firesInYourPlaces
: S.of(context).firesInTheWorld;
print('Building Active Fires');
return Scaffold(
key: _scaffoldKey,
// FIXME new?
drawer: new MainDrawer(context, ActiveFiresPage.routeName),
appBar: new AppBar(
title: Text(title),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {
_scaffoldKey.currentState?.openDrawer();
},
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: new GlobalFiresBottomStats(),
body: view.isLoading
? new FiresSpinner()
: hasLocations
? new Stack(children: <Widget>[
new RefreshIndicator(
child: _buildSavedLocations(
context,
view.yourLocations,
view.onDelete,
view.onToggleSubs,
view.onTap),
onRefresh: () async {
final Completer<Null> completer =
new Completer<Null>();
view.onRefresh(completer);
return completer.future;
}),
SpeedDial(
icon: Icons.add,
activeIcon: Icons.close,
backgroundColor: fires600,
children: _fabMiniMenuItemList(context, view.onAdd),
)
])
: new Center(
child: new CenteredColumn(children: <Widget>[
new RoundedBtn(
icon: Icons.location_searching,
text: S.of(context).addYourCurrentPosition,
onPressed: () => onAddYourLocation(view.onAdd),
backColor: fires600),
const SizedBox(height: 26.0),
new RoundedBtn(
icon: Icons.edit_location,
text: S.of(context).addSomePlace,
onPressed: () => onAddOtherLocation(view.onAdd),
backColor: fires600),
])),
);
});
}
void gotoLocationMap(
Store<AppState> store, YourLocation loc, BuildContext context) {
store.dispatch(new ShowYourLocationMapAction(loc));
Navigator.push(
context, new MaterialPageRoute(builder: (context) => new genericMap()));
}
void onAddYourLocation(AddYourLocationFunction onAdd) {
Future<YourLocation> location = getUserLocation(_scaffoldKey);
_saveLocation(location, onAdd);
}
void onAddOtherLocation(AddYourLocationFunction onAdd) {
Future<YourLocation> location = openPlacesDialog(_scaffoldKey);
_saveLocation(location, onAdd);
}
void _saveLocation(Future<YourLocation> location, onAdd) {
location.then((newLocation) {
if (newLocation != YourLocation.noLocation) {
onAdd(newLocation);
}
});
}
}