Compare commits
3 Commits
59dcaa6cb5
...
8f5ed07be9
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f5ed07be9 | |||
| 6828a9a14b | |||
| 7a45bab7c1 |
@@ -30,12 +30,15 @@ class FloodStation {
|
|||||||
riverName: parseStringValue(json['riverName']),
|
riverName: parseStringValue(json['riverName']),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// sometimes the API returns a String instead of a double
|
||||||
static double parseDoubleValue(dynamic value) {
|
static double parseDoubleValue(dynamic value) {
|
||||||
if (value is double) return value;
|
if (value is double) return value;
|
||||||
if (value is String) return double.parse(value);
|
if (value is String) return double.parse(value);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sometimes the API returns a list of labels that are basically identical
|
||||||
|
/// if [value] is a List, the method return the first item
|
||||||
static String parseStringValue(dynamic value) {
|
static String parseStringValue(dynamic value) {
|
||||||
if (value is String) return value;
|
if (value is String) return value;
|
||||||
if (value is List<dynamic>) return value[0];
|
if (value is List<dynamic>) return value[0];
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ class LandingPage extends StatefulWidget {
|
|||||||
State<LandingPage> createState() => _LandingPageState();
|
State<LandingPage> createState() => _LandingPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// uses mixin OverlayService to show loading overlay
|
||||||
class _LandingPageState extends State<LandingPage> with OverlayService {
|
class _LandingPageState extends State<LandingPage> with OverlayService {
|
||||||
late FloodStationProvider floodStationProvider;
|
late FloodStationProvider _floodStationProvider;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -26,7 +27,7 @@ class _LandingPageState extends State<LandingPage> with OverlayService {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
floodStationProvider = context.watch<FloodStationProvider>();
|
_floodStationProvider = context.watch<FloodStationProvider>();
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
StationFilter(
|
StationFilter(
|
||||||
@@ -37,8 +38,10 @@ class _LandingPageState extends State<LandingPage> with OverlayService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the list of all Stations is empty the method returns a button to load them
|
||||||
|
// else returns a list of FloodStations
|
||||||
Widget _buildStationList() {
|
Widget _buildStationList() {
|
||||||
if (!_shouldShowList()) {
|
if (!_shouldShowList) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
@@ -51,9 +54,9 @@ class _LandingPageState extends State<LandingPage> with OverlayService {
|
|||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: FloodStationListView(
|
child: FloodStationListView(
|
||||||
stations: floodStationProvider.filtered
|
stations: _floodStationProvider.filtered
|
||||||
? floodStationProvider.filteredStations
|
? _floodStationProvider.filteredStations
|
||||||
: floodStationProvider.allStations,
|
: _floodStationProvider.allStations,
|
||||||
onItemTapped: _navigateToStationDetail,
|
onItemTapped: _navigateToStationDetail,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -61,7 +64,7 @@ class _LandingPageState extends State<LandingPage> with OverlayService {
|
|||||||
|
|
||||||
void _handleFilterChange(String filterText) {
|
void _handleFilterChange(String filterText) {
|
||||||
if (filterText.isEmpty) {
|
if (filterText.isEmpty) {
|
||||||
floodStationProvider.filtered = false;
|
_floodStationProvider.filtered = false;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -70,14 +73,14 @@ class _LandingPageState extends State<LandingPage> with OverlayService {
|
|||||||
context: context,
|
context: context,
|
||||||
message: 'Loading',
|
message: 'Loading',
|
||||||
onDismiss: () {
|
onDismiss: () {
|
||||||
floodStationProvider.cancelFilterLoading();
|
_floodStationProvider.cancelFilterLoading();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
floodStationProvider.loadFilteredStations(filterText);
|
_floodStationProvider.loadFilteredStations(filterText);
|
||||||
floodStationProvider.filteredStationsFuture
|
_floodStationProvider.filteredStationsFuture
|
||||||
?.then((_) => removeLoadingNotifier());
|
?.then((_) => removeLoadingNotifier());
|
||||||
floodStationProvider.filtered = true;
|
_floodStationProvider.filtered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleLoadAllStations() {
|
void _handleLoadAllStations() {
|
||||||
@@ -85,25 +88,27 @@ class _LandingPageState extends State<LandingPage> with OverlayService {
|
|||||||
context: context,
|
context: context,
|
||||||
message: 'Loading',
|
message: 'Loading',
|
||||||
onDismiss: () {
|
onDismiss: () {
|
||||||
floodStationProvider.cancelFilterLoading();
|
_floodStationProvider.cancelFilterLoading();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
floodStationProvider
|
_floodStationProvider
|
||||||
.loadAllStations()
|
.loadAllStations()
|
||||||
.whenComplete(() => removeLoadingNotifier());
|
.whenComplete(() => removeLoadingNotifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToStationDetail(FloodStation station) {
|
void _navigateToStationDetail(FloodStation station) {
|
||||||
floodStationProvider.selectedStation = station;
|
_floodStationProvider.selectedStation = station;
|
||||||
Navigator.of(context).pushNamed(FloodStationPage.routeName);
|
Navigator.of(context).pushNamed(FloodStationPage.routeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldShowList() {
|
// returns boolean to decide whether the list of stations should be shown
|
||||||
if (!floodStationProvider.filtered &&
|
// if the list of stations is empty and is not filtered either, the function returns false
|
||||||
floodStationProvider.allStations.isNotEmpty) {
|
bool get _shouldShowList {
|
||||||
|
if (!_floodStationProvider.filtered &&
|
||||||
|
_floodStationProvider.allStations.isNotEmpty) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return floodStationProvider.filtered;
|
return _floodStationProvider.filtered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'map_page.dart';
|
|||||||
|
|
||||||
class MainNavigationScaffold extends StatefulWidget {
|
class MainNavigationScaffold extends StatefulWidget {
|
||||||
const MainNavigationScaffold({super.key});
|
const MainNavigationScaffold({super.key});
|
||||||
|
|
||||||
static const routeName = '/';
|
static const routeName = '/';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -12,13 +13,13 @@ class MainNavigationScaffold extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MainNavigationScaffoldState extends State<MainNavigationScaffold> {
|
class _MainNavigationScaffoldState extends State<MainNavigationScaffold> {
|
||||||
int _selectedPageIndex = 0;
|
|
||||||
|
|
||||||
final List<Widget> _pages = [
|
final List<Widget> _pages = [
|
||||||
const LandingPage(),
|
const LandingPage(),
|
||||||
const MapPage(),
|
const MapPage(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
int _selectedPageIndex = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -32,6 +33,7 @@ class _MainNavigationScaffoldState extends State<MainNavigationScaffold> {
|
|||||||
NavigationDestination(icon: Icon(Icons.map_outlined), label: 'Map'),
|
NavigationDestination(icon: Icon(Icons.map_outlined), label: 'Map'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// Used IndexedStack to save the page state while navigating
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: _selectedPageIndex,
|
index: _selectedPageIndex,
|
||||||
children: _pages,
|
children: _pages,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class MapPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MapPageState extends State<MapPage> {
|
class _MapPageState extends State<MapPage> {
|
||||||
final mapController = MapController();
|
final _mapController = MapController();
|
||||||
late FloodStationProvider _floodStationProvider;
|
late FloodStationProvider _floodStationProvider;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -35,7 +35,7 @@ class _MapPageState extends State<MapPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return FlutterMap(
|
return FlutterMap(
|
||||||
mapController: mapController,
|
mapController: _mapController,
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
cameraConstraint: CameraConstraint.containCenter(
|
cameraConstraint: CameraConstraint.containCenter(
|
||||||
bounds: LatLngBounds.fromPoints(_floodStationProvider.allStations
|
bounds: LatLngBounds.fromPoints(_floodStationProvider.allStations
|
||||||
@@ -47,7 +47,7 @@ class _MapPageState extends State<MapPage> {
|
|||||||
initialZoom: 6,
|
initialZoom: 6,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
openStreetMapTileLayer,
|
_openStreetMapTileLayer,
|
||||||
MarkerClusterLayerWidget(
|
MarkerClusterLayerWidget(
|
||||||
options: MarkerClusterLayerOptions(
|
options: MarkerClusterLayerOptions(
|
||||||
maxClusterRadius: 45,
|
maxClusterRadius: 45,
|
||||||
@@ -56,13 +56,14 @@ class _MapPageState extends State<MapPage> {
|
|||||||
padding: EdgeInsets.all(50),
|
padding: EdgeInsets.all(50),
|
||||||
maxZoom: 15,
|
maxZoom: 15,
|
||||||
markers: _stationsAsMarkers(_floodStationProvider.allStations),
|
markers: _stationsAsMarkers(_floodStationProvider.allStations),
|
||||||
builder: _markerBuilder),
|
builder: _clusterMarkerBuilder),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _markerBuilder(BuildContext context, List<Marker> markers) {
|
// builds the clustered marker
|
||||||
|
Widget _clusterMarkerBuilder(BuildContext context, List<Marker> markers) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
@@ -71,12 +72,13 @@ class _MapPageState extends State<MapPage> {
|
|||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
markers.length.toString(),
|
markers.length.toString(),
|
||||||
style: const TextStyle(color: Colors.white),
|
style: TextStyle(color: Theme.of(context).colorScheme.onTertiary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gets a list of markers from the list of all stations
|
||||||
List<Marker> _stationsAsMarkers(List<FloodStation> stations) {
|
List<Marker> _stationsAsMarkers(List<FloodStation> stations) {
|
||||||
return stations
|
return stations
|
||||||
.map<Marker>(
|
.map<Marker>(
|
||||||
@@ -91,7 +93,7 @@ class _MapPageState extends State<MapPage> {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
_markerTapped(FloodStation station) {
|
void _markerTapped(FloodStation station) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => MapPopup(
|
builder: (context) => MapPopup(
|
||||||
@@ -104,7 +106,7 @@ class _MapPageState extends State<MapPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TileLayer get openStreetMapTileLayer => TileLayer(
|
TileLayer get _openStreetMapTileLayer => TileLayer(
|
||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
|
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
|
||||||
tileProvider: CancellableNetworkTileProvider(),
|
tileProvider: CancellableNetworkTileProvider(),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class Api {
|
|||||||
static const String _rootUrl =
|
static const String _rootUrl =
|
||||||
'https://environment.data.gov.uk/flood-monitoring';
|
'https://environment.data.gov.uk/flood-monitoring';
|
||||||
|
|
||||||
|
/// Fetches all stationszt
|
||||||
static Future<List<FloodStation>> fetchAllStations() async {
|
static Future<List<FloodStation>> fetchAllStations() async {
|
||||||
List<FloodStation> stations = [];
|
List<FloodStation> stations = [];
|
||||||
final response = await http.get(Uri.parse('$_rootUrl/id/stations'));
|
final response = await http.get(Uri.parse('$_rootUrl/id/stations'));
|
||||||
@@ -37,7 +38,6 @@ class Api {
|
|||||||
|
|
||||||
/// [limit] limits the number of entries that are requested from the API and [offset] returns the
|
/// [limit] limits the number of entries that are requested from the API and [offset] returns the
|
||||||
/// list starting from the specified number
|
/// list starting from the specified number
|
||||||
|
|
||||||
static Future<List<FloodStation>> fetchStationsByRange(
|
static Future<List<FloodStation>> fetchStationsByRange(
|
||||||
int limit, int offset) async {
|
int limit, int offset) async {
|
||||||
List<FloodStation> stations = [];
|
List<FloodStation> stations = [];
|
||||||
@@ -52,6 +52,7 @@ class Api {
|
|||||||
return stations;
|
return stations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches all readings from the station with the specified [stationId] from the last 24h
|
||||||
static Future<List<Reading>> fetchReadingsFromStation(
|
static Future<List<Reading>> fetchReadingsFromStation(
|
||||||
String stationId) async {
|
String stationId) async {
|
||||||
List<Reading> readings = [];
|
List<Reading> readings = [];
|
||||||
|
|||||||
@@ -4,19 +4,20 @@ import '../model/flood_station.dart';
|
|||||||
import 'api.dart';
|
import 'api.dart';
|
||||||
|
|
||||||
class FloodStationProvider extends ChangeNotifier {
|
class FloodStationProvider extends ChangeNotifier {
|
||||||
|
// since the getter and setter for the following two fields would be empty, they are publicly accessible
|
||||||
FloodStation? selectedStation;
|
FloodStation? selectedStation;
|
||||||
bool filtered = false;
|
bool filtered = false;
|
||||||
|
|
||||||
List<FloodStation> _allStations = [];
|
List<FloodStation> _allStations = [];
|
||||||
List<FloodStation> _filteredStations = [];
|
List<FloodStation> _filteredStations = [];
|
||||||
|
CancelableOperation? _filteredStationsFuture;
|
||||||
|
|
||||||
List<FloodStation> get allStations => _allStations;
|
List<FloodStation> get allStations => _allStations;
|
||||||
List<FloodStation> get filteredStations => _filteredStations;
|
List<FloodStation> get filteredStations => _filteredStations;
|
||||||
|
|
||||||
CancelableOperation? _filteredStationsFuture;
|
|
||||||
|
|
||||||
CancelableOperation? get filteredStationsFuture => _filteredStationsFuture;
|
CancelableOperation? get filteredStationsFuture => _filteredStationsFuture;
|
||||||
|
|
||||||
|
/// loads all stations in batches of 500 and notifies listeners with every loop except if [silent] = true
|
||||||
|
/// this has lower performance than loading them all at once an shouldn't be used
|
||||||
Future loadAllStationsInBatches({silent = false}) {
|
Future loadAllStationsInBatches({silent = false}) {
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
return Future.doWhile(() async {
|
return Future.doWhile(() async {
|
||||||
@@ -34,6 +35,7 @@ class FloodStationProvider extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// loads all flood stations and notifies listeners when done
|
||||||
Future loadAllStations({silent = false}) {
|
Future loadAllStations({silent = false}) {
|
||||||
return Api.fetchAllStations().then(
|
return Api.fetchAllStations().then(
|
||||||
(value) {
|
(value) {
|
||||||
@@ -45,6 +47,7 @@ class FloodStationProvider extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// loads all stations whose label contains [filter]
|
||||||
Future loadFilteredStations(String filter, {silent = false}) {
|
Future loadFilteredStations(String filter, {silent = false}) {
|
||||||
if (_filteredStationsFuture != null) {
|
if (_filteredStationsFuture != null) {
|
||||||
_filteredStationsFuture!.cancel();
|
_filteredStationsFuture!.cancel();
|
||||||
@@ -61,6 +64,7 @@ class FloodStationProvider extends ChangeNotifier {
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// cancels loading of filtered results.
|
||||||
void cancelFilterLoading() {
|
void cancelFilterLoading() {
|
||||||
if (_filteredStationsFuture != null) {
|
if (_filteredStationsFuture != null) {
|
||||||
_filteredStationsFuture!.cancel();
|
_filteredStationsFuture!.cancel();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ mixin OverlayService {
|
|||||||
final overlayState = Overlay.of(context);
|
final overlayState = Overlay.of(context);
|
||||||
_overlayEntry = OverlayEntry(
|
_overlayEntry = OverlayEntry(
|
||||||
builder: (context) => Positioned(
|
builder: (context) => Positioned(
|
||||||
bottom: 85,
|
bottom: 85, // Positioned above the NavigationBar
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomMarker extends StatelessWidget {
|
class CustomMarker extends StatelessWidget {
|
||||||
const CustomMarker({super.key, required this.onTap});
|
const CustomMarker({super.key, required void Function() onTap})
|
||||||
final void Function() onTap;
|
: _onTap = onTap;
|
||||||
|
|
||||||
|
final void Function() _onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: _onTap,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.location_on_sharp,
|
Icons.location_on_sharp,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ class FloodStationListView extends StatelessWidget {
|
|||||||
const FloodStationListView(
|
const FloodStationListView(
|
||||||
{super.key,
|
{super.key,
|
||||||
required List<FloodStation> stations,
|
required List<FloodStation> stations,
|
||||||
required this.onItemTapped})
|
required void Function(FloodStation) onItemTapped})
|
||||||
: _stations = stations;
|
: _onItemTapped = onItemTapped,
|
||||||
|
_stations = stations;
|
||||||
|
|
||||||
final List<FloodStation> _stations;
|
final List<FloodStation> _stations;
|
||||||
final void Function(FloodStation) onItemTapped;
|
final void Function(FloodStation) _onItemTapped;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -23,7 +24,7 @@ class FloodStationListView extends StatelessWidget {
|
|||||||
final item = _stations.elementAt(index);
|
final item = _stations.elementAt(index);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
isThreeLine: true,
|
isThreeLine: true,
|
||||||
onTap: () => onItemTapped(item),
|
onTap: () => _onItemTapped(item),
|
||||||
title: Text(item.label),
|
title: Text(item.label),
|
||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class LoadingNotifier extends StatelessWidget {
|
class LoadingNotifier extends StatelessWidget {
|
||||||
const LoadingNotifier(
|
const LoadingNotifier(
|
||||||
{super.key, required this.onDismissed, required this.message});
|
{super.key,
|
||||||
final void Function() onDismissed;
|
required void Function() onDismissed,
|
||||||
final String message;
|
required String message})
|
||||||
|
: _onDismissed = onDismissed,
|
||||||
|
_message = message;
|
||||||
|
|
||||||
|
final void Function() _onDismissed;
|
||||||
|
final String _message;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -26,11 +31,11 @@ class LoadingNotifier extends StatelessWidget {
|
|||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
message,
|
_message,
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => onDismissed(),
|
onPressed: () => _onDismissed(),
|
||||||
icon: Icon(Icons.close),
|
icon: Icon(Icons.close),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,14 +5,19 @@ import '../model/flood_station.dart';
|
|||||||
|
|
||||||
class MapPopup extends StatelessWidget {
|
class MapPopup extends StatelessWidget {
|
||||||
const MapPopup(
|
const MapPopup(
|
||||||
{super.key, required this.station, required this.onShowTapped});
|
{super.key,
|
||||||
final FloodStation station;
|
required FloodStation station,
|
||||||
final Function() onShowTapped;
|
required dynamic Function() onShowTapped})
|
||||||
|
: _onShowTapped = onShowTapped,
|
||||||
|
_station = station;
|
||||||
|
|
||||||
|
final FloodStation _station;
|
||||||
|
final Function() _onShowTapped;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(station.label),
|
title: Text(_station.label),
|
||||||
content: Column(
|
content: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -22,14 +27,14 @@ class MapPopup extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(Icons.home_outlined),
|
Icon(Icons.home_outlined),
|
||||||
Padding(padding: EdgeInsets.only(left: 8)),
|
Padding(padding: EdgeInsets.only(left: 8)),
|
||||||
Text(station.town.isEmpty ? '-' : station.town),
|
Text(_station.town.isEmpty ? '-' : _station.town),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.water),
|
Icon(Icons.water),
|
||||||
Padding(padding: EdgeInsets.only(left: 8)),
|
Padding(padding: EdgeInsets.only(left: 8)),
|
||||||
Text(station.riverName.isEmpty ? '-' : station.riverName),
|
Text(_station.riverName.isEmpty ? '-' : _station.riverName),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@@ -37,8 +42,8 @@ class MapPopup extends StatelessWidget {
|
|||||||
Icon(Icons.calendar_month_outlined),
|
Icon(Icons.calendar_month_outlined),
|
||||||
Padding(padding: EdgeInsets.only(left: 8)),
|
Padding(padding: EdgeInsets.only(left: 8)),
|
||||||
Text(
|
Text(
|
||||||
station.dateOpened != null
|
_station.dateOpened != null
|
||||||
? intl.DateFormat.yMd().format(station.dateOpened!)
|
? intl.DateFormat.yMd().format(_station.dateOpened!)
|
||||||
: '-',
|
: '-',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -51,7 +56,7 @@ class MapPopup extends StatelessWidget {
|
|||||||
child: Text('dismiss'),
|
child: Text('dismiss'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: onShowTapped,
|
onPressed: _onShowTapped,
|
||||||
child: Text('show'),
|
child: Text('show'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class ReadingGraph extends StatelessWidget {
|
|||||||
lineBarsData: [
|
lineBarsData: [
|
||||||
LineChartBarData(
|
LineChartBarData(
|
||||||
isCurved: true,
|
isCurved: true,
|
||||||
color: Colors.cyan,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
barWidth: 2,
|
barWidth: 2,
|
||||||
isStrokeCapRound: true,
|
isStrokeCapRound: true,
|
||||||
dotData: const FlDotData(show: false),
|
dotData: const FlDotData(show: false),
|
||||||
|
|||||||
@@ -9,28 +9,28 @@ class StationFilter extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StationFilterState extends State<StationFilter> {
|
class StationFilterState extends State<StationFilter> {
|
||||||
TextEditingController filterController = TextEditingController();
|
final TextEditingController _filterController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: filterController,
|
controller: _filterController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: Icon(Icons.search),
|
prefixIcon: Icon(Icons.search),
|
||||||
suffixIcon: Opacity(
|
suffixIcon: Opacity(
|
||||||
opacity: filterController.text.isEmpty ? 0 : 1,
|
opacity: _filterController.text.isEmpty ? 0 : 1,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
filterController.clear();
|
_filterController.clear();
|
||||||
|
|
||||||
widget.onChanged(filterController.text);
|
widget.onChanged(_filterController.text);
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.delete),
|
icon: Icon(Icons.delete),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: Text('Filter'),
|
label: Text('Filter'),
|
||||||
),
|
),
|
||||||
onChanged: (_) => widget.onChanged(filterController.text),
|
onChanged: (_) => widget.onChanged(_filterController.text),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user