From 7b7e3e9fe00d7322b23349c13c505c5fc66c71f4 Mon Sep 17 00:00:00 2001 From: marco Date: Wed, 29 Jan 2025 13:39:49 +0100 Subject: [PATCH] refactored code to encapsulate date formatting --- lib/pages/flood_station_page.dart | 29 +++++++++++++++++++++++++ lib/pages/map_page.dart | 31 +++++++------------------- lib/services/api.dart | 12 +++++------ lib/services/date_utility.dart | 36 +++++++++++++++++++++++++++++++ lib/widgets/map_popup.dart | 4 ++-- lib/widgets/reading_graph.dart | 16 +++++--------- 6 files changed, 85 insertions(+), 43 deletions(-) create mode 100644 lib/services/date_utility.dart diff --git a/lib/pages/flood_station_page.dart b/lib/pages/flood_station_page.dart index 5dd7bb6..f258633 100644 --- a/lib/pages/flood_station_page.dart +++ b/lib/pages/flood_station_page.dart @@ -16,6 +16,8 @@ class FloodStationPage extends StatefulWidget { } class _FloodStationPageState extends State { + bool _tableVisible = false; + @override void deactivate() { context.read().selectedStation = null; @@ -29,6 +31,14 @@ class _FloodStationPageState extends State { return Scaffold( appBar: AppBar( title: Text(station?.label ?? ''), + actions: [ + TextButton( + onPressed: () => setState(() { + _tableVisible = !_tableVisible; + }), + child: _tableVisible ? Text('Show Graph') : Text('Show Table'), + ), + ], ), body: FutureBuilder>( future: Api.fetchReadingsFromStation(station?.id ?? ''), @@ -36,6 +46,12 @@ class _FloodStationPageState extends State { if (snapshot.hasData) { if (snapshot.data!.isEmpty) { return Center(child: Text('No readings on record.')); + } else if (_tableVisible) { + return SingleChildScrollView( + child: Table( + children: _getTableChildren(snapshot.data!), + ), + ); } return Center( child: Padding( @@ -53,4 +69,17 @@ class _FloodStationPageState extends State { }), ); } + + List _getTableChildren(List list) { + return list + .map( + (e) => TableRow( + children: [ + Text(e.dateTime.toString()), + Text(e.value.toString()), + ], + ), + ) + .toList(); + } } diff --git a/lib/pages/map_page.dart b/lib/pages/map_page.dart index 5a779c7..e48660f 100644 --- a/lib/pages/map_page.dart +++ b/lib/pages/map_page.dart @@ -20,36 +20,22 @@ class MapPage extends StatefulWidget { } class _MapPageState extends State { - final _mapController = MapController(); + final mapController = MapController(); late FloodStationProvider _floodStationProvider; - bool _loading = false; @override Widget build(BuildContext context) { _floodStationProvider = context.watch(); - if (_loading == true) { - return Center( - child: CircularProgressIndicator(), - ); - } else if (_floodStationProvider.allStations.isEmpty) { + if (_floodStationProvider.allStations.isEmpty) { return Center( child: ElevatedButton( - onPressed: () { - setState(() { - _loading = true; - }); - _floodStationProvider - .loadAllStations() - .whenComplete(() => setState(() { - _loading = false; - })); - }, + onPressed: _floodStationProvider.loadAllStations, child: Text('Load Map'), ), ); } return FlutterMap( - mapController: _mapController, + mapController: mapController, options: MapOptions( cameraConstraint: CameraConstraint.containCenter( bounds: LatLngBounds.fromPoints(_floodStationProvider.allStations @@ -70,14 +56,13 @@ class _MapPageState extends State { padding: EdgeInsets.all(50), maxZoom: 15, markers: _stationsAsMarkers(_floodStationProvider.allStations), - builder: _clusterMarkerBuilder), + builder: _markerBuilder), ) ], ); } - // builds the clustered marker - Widget _clusterMarkerBuilder(BuildContext context, List markers) { + Widget _markerBuilder(BuildContext context, List markers) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), @@ -86,7 +71,7 @@ class _MapPageState extends State { child: Center( child: Text( markers.length.toString(), - style: TextStyle(color: Theme.of(context).colorScheme.onTertiary), + style: const TextStyle(color: Colors.white), ), ), ); @@ -107,7 +92,7 @@ class _MapPageState extends State { .toList(); } - void _markerTapped(FloodStation station) { + _markerTapped(FloodStation station) { showDialog( context: context, builder: (context) => MapPopup( diff --git a/lib/services/api.dart b/lib/services/api.dart index 98e53e1..a3538dd 100644 --- a/lib/services/api.dart +++ b/lib/services/api.dart @@ -1,11 +1,13 @@ import 'package:http/http.dart' as http; import 'dart:convert'; -import 'package:timezone/timezone.dart' as tz; import '../model/flood_station.dart'; import '../model/reading.dart'; +import 'date_utility.dart'; class Api { + Api._(); + static const String _rootUrl = 'https://environment.data.gov.uk/flood-monitoring'; @@ -56,7 +58,8 @@ class Api { static Future> fetchReadingsFromStation( String stationId) async { List readings = []; - final dateTime = _getCurrentUKTime().subtract(Duration(days: 1)).toUtc(); + final dateTime = + DateUtility.currentUKTimeUtc.subtract(Duration(days: 1)).toUtc(); final url = '$_rootUrl/id/stations/$stationId/readings?since=${dateTime.toIso8601String()}&_sorted'; final response = await http.get(Uri.parse(url)); @@ -68,9 +71,4 @@ class Api { } return readings.reversed.toList(); } - - static DateTime _getCurrentUKTime() { - final london = tz.getLocation('Europe/London'); - return tz.TZDateTime.now(london); - } } diff --git a/lib/services/date_utility.dart b/lib/services/date_utility.dart new file mode 100644 index 0000000..142bf30 --- /dev/null +++ b/lib/services/date_utility.dart @@ -0,0 +1,36 @@ +import 'package:timezone/timezone.dart' as tz; +import 'package:intl/intl.dart' as intl; + +class DateUtility { + static final intl.DateFormat _hmFormat = intl.DateFormat('Hm'); + static final intl.DateFormat _ymdhmFormat = intl.DateFormat('yyyy-MM-dd H:m'); + static final intl.DateFormat _ymdFormat = intl.DateFormat('yyyy-MM-dd'); + + // private default contructor so class can't be instanciated + DateUtility._(); + + static DateTime get currentUKTimeUtc { + final london = tz.getLocation('Europe/London'); + return tz.TZDateTime.now(london).toUtc(); + } + + /// Formats a date in minutesSinceEpoch to a formatted String of HH:mm + static String formatMinutesToHm(double value) { + return _hmFormat.format( + DateTime.fromMillisecondsSinceEpoch((value * 1000 * 60).toInt())); + } + + static String formatDateToHm(DateTime date) { + return _hmFormat.format(date); + } + + /// Formats a date to yyyy-MM-dd H:m + static String formatDateToYmdhm(DateTime date) { + return _ymdhmFormat.format(date); + } + + /// Formats a date to yyyy-MM-dd + static String formatDateToYmd(DateTime date) { + return _ymdFormat.format(date); + } +} diff --git a/lib/widgets/map_popup.dart b/lib/widgets/map_popup.dart index 0088101..be9fa36 100644 --- a/lib/widgets/map_popup.dart +++ b/lib/widgets/map_popup.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' as intl; import '../model/flood_station.dart'; +import '../services/date_utility.dart'; class MapPopup extends StatelessWidget { const MapPopup( @@ -43,7 +43,7 @@ class MapPopup extends StatelessWidget { Padding(padding: EdgeInsets.only(left: 8)), Text( _station.dateOpened != null - ? intl.DateFormat.yMd().format(_station.dateOpened!) + ? DateUtility.formatDateToYmd(_station.dateOpened!) : '-', ), ], diff --git a/lib/widgets/reading_graph.dart b/lib/widgets/reading_graph.dart index cb7e7c1..7907469 100644 --- a/lib/widgets/reading_graph.dart +++ b/lib/widgets/reading_graph.dart @@ -1,9 +1,9 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' as intl; import 'dart:math'; import '../model/reading.dart'; +import '../services/date_utility.dart'; class ReadingGraph extends StatelessWidget { const ReadingGraph({super.key, required List readings}) @@ -73,7 +73,7 @@ class ReadingGraph extends StatelessWidget { minIncluded: false, getTitlesWidget: (value, meta) => SideTitleWidget( meta: meta, - child: Text(_getDate(value)), + child: Text(DateUtility.formatMinutesToHm(value)), ), ), ); @@ -94,23 +94,17 @@ class ReadingGraph extends StatelessWidget { ); } - String _getDate(double value) { - intl.DateFormat hmFormat = intl.DateFormat('Hm'); - return hmFormat.format( - DateTime.fromMillisecondsSinceEpoch((value * 1000 * 60).toInt())); - } - String _getLongDate(double value) { DateTime date = DateTime.fromMillisecondsSinceEpoch((value * 1000 * 60).toInt()); int daysDifference = (DateTime.now().weekday - date.weekday + 7) % 7; if (daysDifference == 0) { - return 'Today ${intl.DateFormat('Hm').format(date)}'; + return 'Today ${DateUtility.formatDateToHm(date)}'; } else if (daysDifference == 1) { - return 'Yesterday ${intl.DateFormat('Hm').format(date)}'; + return 'Yesterday ${DateUtility.formatDateToHm(date)}'; } - return intl.DateFormat('yyyy-MM-dd H:m').format(date); + return DateUtility.formatDateToYmdhm(date); } }