refactored code to encapsulate date formatting
This commit is contained in:
@@ -16,6 +16,8 @@ class FloodStationPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FloodStationPageState extends State<FloodStationPage> {
|
class _FloodStationPageState extends State<FloodStationPage> {
|
||||||
|
bool _tableVisible = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void deactivate() {
|
void deactivate() {
|
||||||
context.read<FloodStationProvider>().selectedStation = null;
|
context.read<FloodStationProvider>().selectedStation = null;
|
||||||
@@ -29,6 +31,14 @@ class _FloodStationPageState extends State<FloodStationPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(station?.label ?? ''),
|
title: Text(station?.label ?? ''),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
_tableVisible = !_tableVisible;
|
||||||
|
}),
|
||||||
|
child: _tableVisible ? Text('Show Graph') : Text('Show Table'),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder<List<Reading>>(
|
body: FutureBuilder<List<Reading>>(
|
||||||
future: Api.fetchReadingsFromStation(station?.id ?? ''),
|
future: Api.fetchReadingsFromStation(station?.id ?? ''),
|
||||||
@@ -36,6 +46,12 @@ class _FloodStationPageState extends State<FloodStationPage> {
|
|||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
if (snapshot.data!.isEmpty) {
|
if (snapshot.data!.isEmpty) {
|
||||||
return Center(child: Text('No readings on record.'));
|
return Center(child: Text('No readings on record.'));
|
||||||
|
} else if (_tableVisible) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Table(
|
||||||
|
children: _getTableChildren(snapshot.data!),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -53,4 +69,17 @@ class _FloodStationPageState extends State<FloodStationPage> {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<TableRow> _getTableChildren(List<Reading> list) {
|
||||||
|
return list
|
||||||
|
.map<TableRow>(
|
||||||
|
(e) => TableRow(
|
||||||
|
children: [
|
||||||
|
Text(e.dateTime.toString()),
|
||||||
|
Text(e.value.toString()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,36 +20,22 @@ 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;
|
||||||
bool _loading = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_floodStationProvider = context.watch<FloodStationProvider>();
|
_floodStationProvider = context.watch<FloodStationProvider>();
|
||||||
if (_loading == true) {
|
if (_floodStationProvider.allStations.isEmpty) {
|
||||||
return Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
} else if (_floodStationProvider.allStations.isEmpty) {
|
|
||||||
return Center(
|
return Center(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: _floodStationProvider.loadAllStations,
|
||||||
setState(() {
|
|
||||||
_loading = true;
|
|
||||||
});
|
|
||||||
_floodStationProvider
|
|
||||||
.loadAllStations()
|
|
||||||
.whenComplete(() => setState(() {
|
|
||||||
_loading = false;
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
child: Text('Load Map'),
|
child: Text('Load Map'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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
|
||||||
@@ -70,14 +56,13 @@ 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: _clusterMarkerBuilder),
|
builder: _markerBuilder),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// builds the clustered marker
|
Widget _markerBuilder(BuildContext context, List<Marker> markers) {
|
||||||
Widget _clusterMarkerBuilder(BuildContext context, List<Marker> markers) {
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
@@ -86,7 +71,7 @@ class _MapPageState extends State<MapPage> {
|
|||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
markers.length.toString(),
|
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<MapPage> {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _markerTapped(FloodStation station) {
|
_markerTapped(FloodStation station) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => MapPopup(
|
builder: (context) => MapPopup(
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
|
||||||
|
|
||||||
import '../model/flood_station.dart';
|
import '../model/flood_station.dart';
|
||||||
import '../model/reading.dart';
|
import '../model/reading.dart';
|
||||||
|
import 'date_utility.dart';
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
|
Api._();
|
||||||
|
|
||||||
static const String _rootUrl =
|
static const String _rootUrl =
|
||||||
'https://environment.data.gov.uk/flood-monitoring';
|
'https://environment.data.gov.uk/flood-monitoring';
|
||||||
|
|
||||||
@@ -56,7 +58,8 @@ class Api {
|
|||||||
static Future<List<Reading>> fetchReadingsFromStation(
|
static Future<List<Reading>> fetchReadingsFromStation(
|
||||||
String stationId) async {
|
String stationId) async {
|
||||||
List<Reading> readings = [];
|
List<Reading> readings = [];
|
||||||
final dateTime = _getCurrentUKTime().subtract(Duration(days: 1)).toUtc();
|
final dateTime =
|
||||||
|
DateUtility.currentUKTimeUtc.subtract(Duration(days: 1)).toUtc();
|
||||||
final url =
|
final url =
|
||||||
'$_rootUrl/id/stations/$stationId/readings?since=${dateTime.toIso8601String()}&_sorted';
|
'$_rootUrl/id/stations/$stationId/readings?since=${dateTime.toIso8601String()}&_sorted';
|
||||||
final response = await http.get(Uri.parse(url));
|
final response = await http.get(Uri.parse(url));
|
||||||
@@ -68,9 +71,4 @@ class Api {
|
|||||||
}
|
}
|
||||||
return readings.reversed.toList();
|
return readings.reversed.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
static DateTime _getCurrentUKTime() {
|
|
||||||
final london = tz.getLocation('Europe/London');
|
|
||||||
return tz.TZDateTime.now(london);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
lib/services/date_utility.dart
Normal file
36
lib/services/date_utility.dart
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart' as intl;
|
|
||||||
|
|
||||||
import '../model/flood_station.dart';
|
import '../model/flood_station.dart';
|
||||||
|
import '../services/date_utility.dart';
|
||||||
|
|
||||||
class MapPopup extends StatelessWidget {
|
class MapPopup extends StatelessWidget {
|
||||||
const MapPopup(
|
const MapPopup(
|
||||||
@@ -43,7 +43,7 @@ class MapPopup extends StatelessWidget {
|
|||||||
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!)
|
? DateUtility.formatDateToYmd(_station.dateOpened!)
|
||||||
: '-',
|
: '-',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart' as intl;
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import '../model/reading.dart';
|
import '../model/reading.dart';
|
||||||
|
import '../services/date_utility.dart';
|
||||||
|
|
||||||
class ReadingGraph extends StatelessWidget {
|
class ReadingGraph extends StatelessWidget {
|
||||||
const ReadingGraph({super.key, required List<Reading> readings})
|
const ReadingGraph({super.key, required List<Reading> readings})
|
||||||
@@ -73,7 +73,7 @@ class ReadingGraph extends StatelessWidget {
|
|||||||
minIncluded: false,
|
minIncluded: false,
|
||||||
getTitlesWidget: (value, meta) => SideTitleWidget(
|
getTitlesWidget: (value, meta) => SideTitleWidget(
|
||||||
meta: meta,
|
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) {
|
String _getLongDate(double value) {
|
||||||
DateTime date =
|
DateTime date =
|
||||||
DateTime.fromMillisecondsSinceEpoch((value * 1000 * 60).toInt());
|
DateTime.fromMillisecondsSinceEpoch((value * 1000 * 60).toInt());
|
||||||
int daysDifference = (DateTime.now().weekday - date.weekday + 7) % 7;
|
int daysDifference = (DateTime.now().weekday - date.weekday + 7) % 7;
|
||||||
|
|
||||||
if (daysDifference == 0) {
|
if (daysDifference == 0) {
|
||||||
return 'Today ${intl.DateFormat('Hm').format(date)}';
|
return 'Today ${DateUtility.formatDateToHm(date)}';
|
||||||
} else if (daysDifference == 1) {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user