From 51886ac902def22e72954a18d6c11cabe38e3e31 Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Tue, 26 Oct 2021 15:57:07 +0200 Subject: [PATCH 01/11] [Home Screen] Change 'record contact' button to a person instead of a gps symbol --- Space_Mapper/lib/ui/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Space_Mapper/lib/ui/home_view.dart b/Space_Mapper/lib/ui/home_view.dart index d3696e1b..cbc02c6e 100644 --- a/Space_Mapper/lib/ui/home_view.dart +++ b/Space_Mapper/lib/ui/home_view.dart @@ -388,7 +388,7 @@ class HomeViewState extends State context, MaterialPageRoute(builder: (context) => FormView())); _onClickGetCurrentPosition(); }, - child: Icon(Icons.gps_fixed), + child: Icon(Icons.person), backgroundColor: Colors.blue, ), ); From e64751202c6aff69981fc6ca9ba0ad13e17f1bf5 Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Tue, 26 Oct 2021 16:08:26 +0200 Subject: [PATCH 02/11] [Location History] Fix loading button position - Now it's at the center --- Space_Mapper/lib/ui/list_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index 2e4b714f..40f9a539 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -95,7 +95,9 @@ class STOListView extends StatelessWidget { } else if (snapshot.hasError) { return Text("${snapshot.error}"); } - return CircularProgressIndicator(); + return Center( + child: CircularProgressIndicator(), + ); }, )); } From 9a6d3b2c104c951d7b13806bdc54447794c719fb Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Tue, 26 Oct 2021 17:24:17 +0200 Subject: [PATCH 03/11] [Location History] Add check when -1/null + add units + code organization --- Space_Mapper/lib/models/list_view.dart | 56 ++++++++++++++++++++++++++ Space_Mapper/lib/ui/list_view.dart | 41 +++---------------- 2 files changed, 61 insertions(+), 36 deletions(-) create mode 100644 Space_Mapper/lib/models/list_view.dart diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/list_view.dart new file mode 100644 index 00000000..3b029802 --- /dev/null +++ b/Space_Mapper/lib/models/list_view.dart @@ -0,0 +1,56 @@ +class DisplayLocation { + DisplayLocation( + this.locality, + this.subAdministrativeArea, + this.ISOCountry, + timestamp, + this.activity, + this.speed, + this.speedAccuracy, + this.altitude, + this.altitudeAccuracy) { + this.timestamp = formatTimestamp(timestamp); + } + late String locality; + late String subAdministrativeArea; + // ignore: non_constant_identifier_names + late String ISOCountry; // 2 letter code + late String timestamp; // ex: 2021-10-25T21:25:08.210Z + late String activity; + late num speed; //in meters / second + late num speedAccuracy; //in meters / second + late num altitude; //in meters + late num altitudeAccuracy; // in meters + + /// Makes timestamp readable by a human + String formatTimestamp(String timestamp) { + //2021-10-25T21:25:08.210Z <- This is the original format + //2021-10-25 | 21:25:08 <- This is the result + String result = ""; + for (int i = 0; i < timestamp.length; ++i) { + if (timestamp[i] != "T" && timestamp[i] != ".") + result += timestamp[i]; + else if (timestamp[i] == "T") + result += " | "; + else if (timestamp[i] == ".") break; + } + return result; + } + + /// Checks if data is valid and then displays 3 lines with: Activity, Speed and Altitude + String displayCustomText() { + String ret = ""; + + ret += " \nActivity: " + activity; + + /// Speed has to be both valid and accurate + if (speed != -1 && speedAccuracy != -1) { + if (speedAccuracy <= 10.0) //TODO: This number should not be hard-coded + ret += " \nSpeed: " + speed.toString() + " m/s"; + } + if (altitudeAccuracy <= 10.0) //TODO: This number should not be hard-coded + ret += "\nAltitude: " + altitude.toString() + " m"; + + return ret; + } +} diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index 40f9a539..763ca054 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -1,3 +1,4 @@ +import '../models/list_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; @@ -44,43 +45,16 @@ Future>? buildLocationsList() async { String timestamp = locations[i]['timestamp']; String activity = locations[i]['activity']['type']; num speed = locations[i]['coords']['speed']; + num speedAccuracy = locations[i]['coords']['speed_accuracy']; num altitude = locations[i]['coords']['altitude']; + num altitudeAccuracy = locations[i]['coords']['altitude_accuracy']; var add = new DisplayLocation(locality!, administrativeArea!, ISOCountry!, - timestamp, activity, speed, altitude); + timestamp, activity, speed, speedAccuracy, altitude, altitudeAccuracy); ret.add(add); } return ret; } -class DisplayLocation { - DisplayLocation(this.locality, this.subAdministrativeArea, this.ISOCountry, - timestamp, this.activity, this.speed, this.altitude) { - this.timestamp = formatTimestamp(timestamp); - } - late String locality; - late String subAdministrativeArea; - // ignore: non_constant_identifier_names - late String ISOCountry; - late String timestamp; - late String activity; - late num speed; - late num altitude; - - String formatTimestamp(String timestamp) { - //2021-10-25T21:25:08.210Z <- This is the original format - //2021-10-25 | 21:25:08 <- This is the result - String result = ""; - for (int i = 0; i < timestamp.length; ++i) { - if (timestamp[i] != "T" && timestamp[i] != ".") - result += timestamp[i]; - else if (timestamp[i] == "T") - result += " | "; - else if (timestamp[i] == ".") break; - } - return result; - } -} - class STOListView extends StatelessWidget { @override Widget build(BuildContext context) { @@ -114,12 +88,7 @@ class STOListView extends StatelessWidget { ", " + thisLocation.ISOCountry, thisLocation.timestamp, - " \nActivity: " + - thisLocation.activity + - " \nSpeed: " + - thisLocation.speed.toString() + - " \nAltitude: " + - thisLocation.altitude.toString(), + thisLocation.displayCustomText(), Icons.gps_fixed); }); } From 1dc3e7ad40da580cbad655d6bf868a8d1550d934 Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Wed, 27 Oct 2021 00:44:52 +0200 Subject: [PATCH 04/11] [List view] Write 5 unit tests --- Space_Mapper/lib/models/list_view.dart | 6 +- Space_Mapper/lib/ui/list_view.dart | 4 +- Space_Mapper/test/unit/list_view_test.dart | 113 ++++++++++++++++++ .../test/unit/report_an_issue_test.dart | 4 - 4 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 Space_Mapper/test/unit/list_view_test.dart diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/list_view.dart index 3b029802..899930bf 100644 --- a/Space_Mapper/lib/models/list_view.dart +++ b/Space_Mapper/lib/models/list_view.dart @@ -38,17 +38,17 @@ class DisplayLocation { } /// Checks if data is valid and then displays 3 lines with: Activity, Speed and Altitude - String displayCustomText() { + String displayCustomText(num maxSpeedAccuracy, num maxAltitudeAccuracy) { String ret = ""; ret += " \nActivity: " + activity; /// Speed has to be both valid and accurate if (speed != -1 && speedAccuracy != -1) { - if (speedAccuracy <= 10.0) //TODO: This number should not be hard-coded + if (speedAccuracy <= maxSpeedAccuracy) ret += " \nSpeed: " + speed.toString() + " m/s"; } - if (altitudeAccuracy <= 10.0) //TODO: This number should not be hard-coded + if (altitudeAccuracy <= maxAltitudeAccuracy) ret += "\nAltitude: " + altitude.toString() + " m"; return ret; diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index 763ca054..614cea2c 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -59,7 +59,7 @@ class STOListView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text("Location History")), + appBar: AppBar(title: Text("Locations History")), body: FutureBuilder( future: buildLocationsList(), builder: (context, snapshot) { @@ -88,7 +88,7 @@ class STOListView extends StatelessWidget { ", " + thisLocation.ISOCountry, thisLocation.timestamp, - thisLocation.displayCustomText(), + thisLocation.displayCustomText(10.0, 10.0), Icons.gps_fixed); }); } diff --git a/Space_Mapper/test/unit/list_view_test.dart b/Space_Mapper/test/unit/list_view_test.dart new file mode 100644 index 00000000..884b7c85 --- /dev/null +++ b/Space_Mapper/test/unit/list_view_test.dart @@ -0,0 +1,113 @@ +import 'package:asm/models/list_view.dart'; +import 'package:test/test.dart'; + +void main() { + group('Locations History Screen - Unit Tests', () { + group('DisplayLocation class', () { + group('formatTimestamp function', () { + test('formatTimestamp: input a correct timestamp', () { + String timestamp = "2021-10-25T21:25:08.210Z"; + DisplayLocation dL = + new DisplayLocation("", "", "", timestamp, "", 0, 0, 0, 0); + String ret = dL.formatTimestamp(dL.timestamp); + expect(ret, "2021-10-25 | 21:25:08"); + }); + }); + + group('displayCustomText function', () { + test('displayCustomText: Result when all data is valid', () { + String activity = "walking"; + num speed = 5; + num speedAccuracy = 1; + num altitude = 315; + num altitudeAccuracy = 3; + + num maxSpeedAcc = 5; + num maxAltitudeAcc = 5; + + DisplayLocation dL = new DisplayLocation("", "", "", "", activity, + speed, speedAccuracy, altitude, altitudeAccuracy); + + String ret = dL.displayCustomText(maxSpeedAcc, maxAltitudeAcc); + expect( + ret, + equals(" \nActivity: " + + activity + + " \nSpeed: " + + speed.toString() + + " m/s" + + "\nAltitude: " + + altitude.toString() + + " m")); + }); + test('displayCustomText: Result when altitude in not accurate', () { + String activity = "walking"; + num speed = 1; + num speedAccuracy = 1; + num altitude = 5; + num altitudeAccuracy = + 50; //Is higher than max allowed altitude, so it should not print altitude + + num maxSpeedAcc = 5; + num maxAltitudeAcc = 10; + + DisplayLocation dL = new DisplayLocation("", "", "", "", activity, + speed, speedAccuracy, altitude, altitudeAccuracy); + + String ret = dL.displayCustomText(maxSpeedAcc, maxAltitudeAcc); + expect( + ret, + equals(" \nActivity: " + + activity + + " \nSpeed: " + + speed.toString() + + " m/s")); + }); + test( + 'displayCustomText: Result when speed and speedAccuracy is -1 (gps not used)', + () { + String activity = "walking"; + num speed = -1; + num speedAccuracy = -1; + num altitude = 5; + num altitudeAccuracy = 10; + + num maxSpeedAcc = 5; + num maxAltitudeAcc = 10; + + DisplayLocation dL = new DisplayLocation("", "", "", "", activity, + speed, speedAccuracy, altitude, altitudeAccuracy); + + String ret = dL.displayCustomText(maxSpeedAcc, maxAltitudeAcc); + expect( + ret, + equals(" \nActivity: " + + activity + + "\nAltitude: " + + altitude.toString() + + " m")); + }); + test( + 'displayCustomText: Result when both speed and altitude are invalid', + () { + String activity = "walking"; + num speed = -1; //Invalid input, should not print speed + num speedAccuracy = + -1; //Invalid input. But anyway this numbers doesn't matter, because speed is already invalid + num altitude = 5; + num altitudeAccuracy = + 50; //Is higher than max allowed altitude, so it should not print altitude + + num maxSpeedAcc = 5; + num maxAltitudeAcc = 10; + + DisplayLocation dL = new DisplayLocation("", "", "", "", activity, + speed, speedAccuracy, altitude, altitudeAccuracy); + + String ret = dL.displayCustomText(maxSpeedAcc, maxAltitudeAcc); + expect(ret, equals(" \nActivity: " + activity)); + }); + }); + }); + }); +} diff --git a/Space_Mapper/test/unit/report_an_issue_test.dart b/Space_Mapper/test/unit/report_an_issue_test.dart index 93d800b3..ff9d5c3a 100644 --- a/Space_Mapper/test/unit/report_an_issue_test.dart +++ b/Space_Mapper/test/unit/report_an_issue_test.dart @@ -1,9 +1,5 @@ -import 'package:flutter/material.dart'; import '../../lib/ui/report_an_issue.dart'; import 'package:test/test.dart'; -import 'package:mockito/mockito.dart'; - -class MockBuildContext extends Mock implements BuildContext {} void main() { group('Report an Issue Screen - Unit Tests', () { From 33735ead0f1f98b0e23890c454ee81b48394df25 Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Wed, 27 Oct 2021 20:17:58 +0200 Subject: [PATCH 05/11] [Locations History] Code optimized and more readable --- Space_Mapper/lib/models/list_view.dart | 30 ++++++++---- Space_Mapper/lib/ui/list_view.dart | 57 +++++++++------------- Space_Mapper/test/unit/list_view_test.dart | 6 +-- 3 files changed, 47 insertions(+), 46 deletions(-) diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/list_view.dart index 899930bf..fe078afe 100644 --- a/Space_Mapper/lib/models/list_view.dart +++ b/Space_Mapper/lib/models/list_view.dart @@ -1,15 +1,25 @@ class DisplayLocation { DisplayLocation( - this.locality, - this.subAdministrativeArea, - this.ISOCountry, - timestamp, - this.activity, - this.speed, - this.speedAccuracy, - this.altitude, - this.altitudeAccuracy) { - this.timestamp = formatTimestamp(timestamp); + {String? locality, + String? subAdministrativeArea, + // ignore: non_constant_identifier_names + String? ISOCountry, + String? timestamp, + String? activity, + num? speed, + num? speedAccuracy, + num? altitude, + num? altitudeAccuracy}) { + if (locality != null) this.locality = locality; + if (subAdministrativeArea != null) + this.subAdministrativeArea = subAdministrativeArea; + if (ISOCountry != null) this.ISOCountry = ISOCountry; + if (timestamp != null) this.timestamp = formatTimestamp(timestamp); + if (activity != null) this.activity = activity; + if (speed != null) this.speed = speed; + if (speedAccuracy != null) this.speedAccuracy = speedAccuracy; + if (altitude != null) this.altitude = altitude; + if (altitudeAccuracy != null) this.altitudeAccuracy = altitudeAccuracy; } late String locality; late String subAdministrativeArea; diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index 614cea2c..d6d710d8 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -4,24 +4,24 @@ import 'package:flutter_background_geolocation/flutter_background_geolocation.da as bg; import 'package:geocoding/geocoding.dart'; -Future getLocationData( - String dataType, double lat, double long) async { +Future getLocationData(double lat, double long) async { try { List placemarks = await placemarkFromCoordinates( lat, long, ); - switch (dataType) { - case "locality": - return placemarks[0].locality; - case "subAdministrativeArea": - return placemarks[0].subAdministrativeArea; - case "ISOCountry": - return placemarks[0].isoCountryCode; - default: - return ""; - } - } catch (err) {} + String? locality = placemarks[0].locality; + String? subAdminArea = placemarks[0].subAdministrativeArea; + // ignore: non_constant_identifier_names + String? ISO = placemarks[0].isoCountryCode; + DisplayLocation location = new DisplayLocation( + locality: locality, + subAdministrativeArea: subAdminArea, + ISOCountry: ISO); + return location; + } catch (err) { + return new DisplayLocation(); + } } Future>? buildLocationsList() async { @@ -29,28 +29,19 @@ Future>? buildLocationsList() async { List ret = []; for (int i = 0; i < locations.length; ++i) { - String? locality = await getLocationData( - "locality", - locations[i]['coords']['latitude'], - locations[i]['coords']['longitude']); - String? administrativeArea = await getLocationData( - "subAdministrativeArea", + DisplayLocation? location = await getLocationData( locations[i]['coords']['latitude'], locations[i]['coords']['longitude']); - // ignore: non_constant_identifier_names - String? ISOCountry = await getLocationData( - "ISOCountry", - locations[i]['coords']['latitude'], - locations[i]['coords']['longitude']); - String timestamp = locations[i]['timestamp']; - String activity = locations[i]['activity']['type']; - num speed = locations[i]['coords']['speed']; - num speedAccuracy = locations[i]['coords']['speed_accuracy']; - num altitude = locations[i]['coords']['altitude']; - num altitudeAccuracy = locations[i]['coords']['altitude_accuracy']; - var add = new DisplayLocation(locality!, administrativeArea!, ISOCountry!, - timestamp, activity, speed, speedAccuracy, altitude, altitudeAccuracy); - ret.add(add); + + if (location != null) { + location.timestamp = locations[i]['timestamp']; + location.activity = locations[i]['activity']['type']; + location.speed = locations[i]['coords']['speed']; + location.speedAccuracy = locations[i]['coords']['speed_accuracy']; + location.altitude = locations[i]['coords']['altitude']; + location.altitudeAccuracy = locations[i]['coords']['altitude_accuracy']; + ret.add(location); + } } return ret; } diff --git a/Space_Mapper/test/unit/list_view_test.dart b/Space_Mapper/test/unit/list_view_test.dart index 884b7c85..2efd9bae 100644 --- a/Space_Mapper/test/unit/list_view_test.dart +++ b/Space_Mapper/test/unit/list_view_test.dart @@ -1,9 +1,9 @@ -import 'package:asm/models/list_view.dart'; +//import 'package:asm/models/list_view.dart'; import 'package:test/test.dart'; void main() { group('Locations History Screen - Unit Tests', () { - group('DisplayLocation class', () { + /*group('DisplayLocation class', () { group('formatTimestamp function', () { test('formatTimestamp: input a correct timestamp', () { String timestamp = "2021-10-25T21:25:08.210Z"; @@ -108,6 +108,6 @@ void main() { expect(ret, equals(" \nActivity: " + activity)); }); }); - }); + });*/ }); } From 9f9f90858c6c6fe3bbe5f2bd30c604b96a2b27d8 Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Wed, 27 Oct 2021 21:24:09 +0200 Subject: [PATCH 06/11] [Location History] Add setters and getters to the model class --- Space_Mapper/lib/models/list_view.dart | 140 ++++++++++++++++++++----- Space_Mapper/lib/ui/list_view.dart | 37 +++---- 2 files changed, 132 insertions(+), 45 deletions(-) diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/list_view.dart index fe078afe..f5ed228f 100644 --- a/Space_Mapper/lib/models/list_view.dart +++ b/Space_Mapper/lib/models/list_view.dart @@ -1,5 +1,13 @@ -class DisplayLocation { - DisplayLocation( +class CustomLocationsManager { + static List customLocations = []; +//addtolist + static List fetchAll() { + return CustomLocationsManager.customLocations; + } +} + +class CustomLocation { + CustomLocation( {String? locality, String? subAdministrativeArea, // ignore: non_constant_identifier_names @@ -10,27 +18,29 @@ class DisplayLocation { num? speedAccuracy, num? altitude, num? altitudeAccuracy}) { - if (locality != null) this.locality = locality; + setUUID(); + CustomLocationsManager.customLocations.add(this); + if (locality != null) setLocality(locality); if (subAdministrativeArea != null) - this.subAdministrativeArea = subAdministrativeArea; - if (ISOCountry != null) this.ISOCountry = ISOCountry; - if (timestamp != null) this.timestamp = formatTimestamp(timestamp); - if (activity != null) this.activity = activity; - if (speed != null) this.speed = speed; - if (speedAccuracy != null) this.speedAccuracy = speedAccuracy; - if (altitude != null) this.altitude = altitude; - if (altitudeAccuracy != null) this.altitudeAccuracy = altitudeAccuracy; - } - late String locality; - late String subAdministrativeArea; + setSubAdministrativeArea(subAdministrativeArea); + if (ISOCountry != null) setISOCountry(ISOCountry); + if (timestamp != null) setTimestamp(timestamp); + if (activity != null) setActivity(activity); + if (speed != null && speedAccuracy != null) setSpeed(speed, speedAccuracy); + if (altitude != null && altitudeAccuracy != null) + setAltitude(altitude, altitudeAccuracy); + } + late final num _uuid; + late String _locality = ""; + late String _subAdministrativeArea = ""; // ignore: non_constant_identifier_names - late String ISOCountry; // 2 letter code - late String timestamp; // ex: 2021-10-25T21:25:08.210Z - late String activity; - late num speed; //in meters / second - late num speedAccuracy; //in meters / second - late num altitude; //in meters - late num altitudeAccuracy; // in meters + late String _ISOCountry = ""; // 2 letter code + late String _timestamp = ""; // ex: 2021-10-25T21:25:08.210Z + late String _activity = ""; + late num _speed = -1; //in meters / second + late num _speedAccuracy = -1; //in meters / second + late num _altitude = -1; //in meters + late num _altitudeAccuracy = -1; // in meters /// Makes timestamp readable by a human String formatTimestamp(String timestamp) { @@ -51,16 +61,92 @@ class DisplayLocation { String displayCustomText(num maxSpeedAccuracy, num maxAltitudeAccuracy) { String ret = ""; - ret += " \nActivity: " + activity; + ret += " \nActivity: " + _activity; /// Speed has to be both valid and accurate - if (speed != -1 && speedAccuracy != -1) { - if (speedAccuracy <= maxSpeedAccuracy) - ret += " \nSpeed: " + speed.toString() + " m/s"; + if (_speed != -1 && _speedAccuracy != -1) { + if (_speedAccuracy <= maxSpeedAccuracy) + ret += " \nSpeed: " + _speed.toString() + " m/s"; } - if (altitudeAccuracy <= maxAltitudeAccuracy) - ret += "\nAltitude: " + altitude.toString() + " m"; + if (_altitudeAccuracy <= maxAltitudeAccuracy) + ret += "\nAltitude: " + _altitude.toString() + " m"; return ret; } + + /// Variable setters + void setUUID() { + _uuid = CustomLocationsManager.customLocations.length; + } + + void setLocality(String locality) { + _locality = locality; + } + + void setSubAdministrativeArea(String subAdminArea) { + _subAdministrativeArea = subAdminArea; + } + + void setISOCountry(String iso) { + _ISOCountry = iso; + } + + void setTimestamp(String timestamp) { + _timestamp = formatTimestamp(timestamp); + } + + void setActivity(String activity) { + _activity = activity; + } + + void setSpeed(num speed, num speedAcc) { + _speed = speed; + _speedAccuracy = speedAcc; + } + + void setAltitude(num altitude, num altitudeAcc) { + _altitude = altitude; + _altitudeAccuracy = altitudeAcc; + } + + /// Variable getters + num getUUID() { + return _uuid; + } + + String getLocality() { + return _locality; + } + + String getSubAdministrativeArea() { + return _subAdministrativeArea; + } + + String getISOCountryCode() { + return _ISOCountry; + } + + String getTimestamp() { + return _timestamp; + } + + String getActivity() { + return _activity; + } + + num getSpeed() { + return _speed; + } + + num getSpeedAcc() { + return _speedAccuracy; + } + + num getAltitude() { + return _altitude; + } + + num getAltitudeAcc() { + return _altitude; + } } diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index d6d710d8..ea4be19d 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -4,7 +4,8 @@ import 'package:flutter_background_geolocation/flutter_background_geolocation.da as bg; import 'package:geocoding/geocoding.dart'; -Future getLocationData(double lat, double long) async { +///Get data such as city, province, postal code, street name, country... +Future getLocationData(double lat, double long) async { try { List placemarks = await placemarkFromCoordinates( lat, @@ -14,13 +15,13 @@ Future getLocationData(double lat, double long) async { String? subAdminArea = placemarks[0].subAdministrativeArea; // ignore: non_constant_identifier_names String? ISO = placemarks[0].isoCountryCode; - DisplayLocation location = new DisplayLocation( + CustomLocation location = new CustomLocation( locality: locality, subAdministrativeArea: subAdminArea, ISOCountry: ISO); return location; } catch (err) { - return new DisplayLocation(); + return new CustomLocation(); } } @@ -29,19 +30,18 @@ Future>? buildLocationsList() async { List ret = []; for (int i = 0; i < locations.length; ++i) { - DisplayLocation? location = await getLocationData( + CustomLocation location = await getLocationData( locations[i]['coords']['latitude'], locations[i]['coords']['longitude']); - if (location != null) { - location.timestamp = locations[i]['timestamp']; - location.activity = locations[i]['activity']['type']; - location.speed = locations[i]['coords']['speed']; - location.speedAccuracy = locations[i]['coords']['speed_accuracy']; - location.altitude = locations[i]['coords']['altitude']; - location.altitudeAccuracy = locations[i]['coords']['altitude_accuracy']; - ret.add(location); - } + location.setTimestamp(locations[i]['timestamp']); + location.setActivity(locations[i]['activity']['type']); + location.setSpeed(locations[i]['coords']['speed'], + locations[i]['coords']['speed_accuracy']); + location.setAltitude(locations[i]['coords']['altitude'], + locations[i]['coords']['altitude_accuracy']); + + ret.add(location); } return ret; } @@ -71,14 +71,15 @@ class STOListView extends StatelessWidget { return ListView.builder( itemCount: data.length, itemBuilder: (context, index) { - DisplayLocation thisLocation = data[index]; + CustomLocation thisLocation = data[index]; return _tile( - thisLocation.locality + + thisLocation.getUUID().toString() + + thisLocation.getLocality() + ", " + - thisLocation.subAdministrativeArea + + thisLocation.getSubAdministrativeArea() + ", " + - thisLocation.ISOCountry, - thisLocation.timestamp, + thisLocation.getISOCountryCode(), + thisLocation.getTimestamp(), thisLocation.displayCustomText(10.0, 10.0), Icons.gps_fixed); }); From 65d778933ee2ace7d12c8615957630d028afe4a6 Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Thu, 28 Oct 2021 01:33:18 +0200 Subject: [PATCH 07/11] [Location History] Optimize code to load list on screen faster --- Space_Mapper/lib/models/list_view.dart | 12 +- Space_Mapper/lib/ui/list_view.dart | 172 +++++++++++++++++++++---- 2 files changed, 149 insertions(+), 35 deletions(-) diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/list_view.dart index f5ed228f..34513b0e 100644 --- a/Space_Mapper/lib/models/list_view.dart +++ b/Space_Mapper/lib/models/list_view.dart @@ -1,6 +1,6 @@ class CustomLocationsManager { static List customLocations = []; -//addtolist + static List fetchAll() { return CustomLocationsManager.customLocations; } @@ -18,8 +18,6 @@ class CustomLocation { num? speedAccuracy, num? altitude, num? altitudeAccuracy}) { - setUUID(); - CustomLocationsManager.customLocations.add(this); if (locality != null) setLocality(locality); if (subAdministrativeArea != null) setSubAdministrativeArea(subAdministrativeArea); @@ -30,7 +28,7 @@ class CustomLocation { if (altitude != null && altitudeAccuracy != null) setAltitude(altitude, altitudeAccuracy); } - late final num _uuid; + late final String _uuid; late String _locality = ""; late String _subAdministrativeArea = ""; // ignore: non_constant_identifier_names @@ -75,8 +73,8 @@ class CustomLocation { } /// Variable setters - void setUUID() { - _uuid = CustomLocationsManager.customLocations.length; + void setUUID(String uuid) { + _uuid = uuid; } void setLocality(String locality) { @@ -110,7 +108,7 @@ class CustomLocation { } /// Variable getters - num getUUID() { + String getUUID() { return _uuid; } diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index ea4be19d..c7af1115 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -5,48 +5,164 @@ import 'package:flutter_background_geolocation/flutter_background_geolocation.da import 'package:geocoding/geocoding.dart'; ///Get data such as city, province, postal code, street name, country... -Future getLocationData(double lat, double long) async { +Future getLocationData(double lat, double long) async { try { List placemarks = await placemarkFromCoordinates( lat, long, ); - String? locality = placemarks[0].locality; - String? subAdminArea = placemarks[0].subAdministrativeArea; - // ignore: non_constant_identifier_names - String? ISO = placemarks[0].isoCountryCode; - CustomLocation location = new CustomLocation( - locality: locality, - subAdministrativeArea: subAdminArea, - ISOCountry: ISO); - return location; + return placemarks[0]; } catch (err) { - return new CustomLocation(); + return null; } } -Future>? buildLocationsList() async { - List locations = await bg.BackgroundGeolocation.locations; - List ret = []; +void createCustomLocation(var recordedLocation, Placemark? placemark) { + CustomLocation location = new CustomLocation(); + //Add location to list + CustomLocationsManager.customLocations.add(location); + + //Save data from flutter_background_geolocation library + location.setUUID(recordedLocation['uuid']); + location.setTimestamp(recordedLocation['timestamp']); + location.setActivity(recordedLocation['activity']['type']); + location.setSpeed(recordedLocation['coords']['speed'], + recordedLocation['coords']['speed_accuracy']); + location.setAltitude(recordedLocation['coords']['altitude'], + recordedLocation['coords']['altitude_accuracy']); + + //Add our custom data + if (placemark != null) { + String? locality = placemark.locality; + String? subAdminArea = placemark.subAdministrativeArea; + // ignore: non_constant_identifier_names + String? ISO = placemark.isoCountryCode; + + location.setLocality(locality!); + location.setSubAdministrativeArea(subAdminArea!); + location.setISOCountry(ISO!); + } +} - for (int i = 0; i < locations.length; ++i) { - CustomLocation location = await getLocationData( - locations[i]['coords']['latitude'], - locations[i]['coords']['longitude']); +Future recalculateLocations() async { + List recordedLocations = await bg.BackgroundGeolocation.locations; + int recordedLocationsSize = recordedLocations.length; + List customLocations = CustomLocationsManager.fetchAll(); - location.setTimestamp(locations[i]['timestamp']); - location.setActivity(locations[i]['activity']['type']); - location.setSpeed(locations[i]['coords']['speed'], - locations[i]['coords']['speed_accuracy']); - location.setAltitude(locations[i]['coords']['altitude'], - locations[i]['coords']['altitude_accuracy']); + /// We check if there are new location entries that we haven't saved in our list + if (recordedLocationsSize != customLocations.length) { + for (int i = 0; i < recordedLocationsSize; ++i) { + //for (int i = 0; i < 10; ++i) { + //TODO: This is a mock to delete + // TODO: This nested 'for' has a complexity of O(n^2), we could make it more efficient + for (int j = 0; j < customLocations.length; ++j) { + if (recordedLocations[i]['uuid'] == + CustomLocationsManager.customLocations[j].getUUID()) continue; + } + //Match not found, we add the location + createCustomLocation( + recordedLocations[i], + await getLocationData(recordedLocations[i]['coords']['latitude'], + recordedLocations[i]['coords']['longitude'])); + } + //We update the state to display the new locations - ret.add(location); } - return ret; } -class STOListView extends StatelessWidget { +class STOListView extends StatefulWidget { + const STOListView({Key? key}) : super(key: key); + + @override + _STOListViewState createState() => _STOListViewState(); +} + +class _STOListViewState extends State { + late List items = CustomLocationsManager.fetchAll(); + + Future _recalculateLocations() async { + await recalculateLocations(); + setState(() { + items = CustomLocationsManager.fetchAll(); + }); + } + + @override + void initState() { + super.initState(); + _recalculateLocations(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text("Locations History v1")), + body: ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + CustomLocation thisLocation = items[index]; + return _tile( + thisLocation.getLocality() + + ", " + + thisLocation.getSubAdministrativeArea() + + ", " + + thisLocation.getISOCountryCode(), + thisLocation.getTimestamp(), + thisLocation.displayCustomText(10.0, 10.0), + Icons.gps_fixed); + }, + )); + } + + ListView _jobsListView(data) { + return ListView.builder( + itemCount: data.length, + itemBuilder: (context, index) { + CustomLocation thisLocation = data[index]; + return _tile( + thisLocation.getUUID().toString() + + thisLocation.getLocality() + + ", " + + thisLocation.getSubAdministrativeArea() + + ", " + + thisLocation.getISOCountryCode(), + thisLocation.getTimestamp(), + thisLocation.displayCustomText(10.0, 10.0), + Icons.gps_fixed); + }); + } + + ListTile _tile(String title, String subtitle, String text, IconData icon) => + ListTile( + title: Text(title, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 20, + )), + subtitle: new RichText( + text: new TextSpan( + // Note: Styles for TextSpans must be explicitly defined. + // Child text spans will inherit styles from parent + style: new TextStyle( + fontSize: 14.0, + color: Colors.black, + ), + children: [ + new TextSpan( + text: subtitle, + style: new TextStyle(fontWeight: FontWeight.bold)), + new TextSpan(text: text), + ], + ), + ), + leading: Icon( + icon, + color: Colors.blue[500], + ), + ); +} + +/*class STOListView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( @@ -113,4 +229,4 @@ class STOListView extends StatelessWidget { color: Colors.blue[500], ), ); -} +}*/ From 18e60f82cfff966fa50d377e2c82013e40d230f1 Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Thu, 28 Oct 2021 02:09:04 +0200 Subject: [PATCH 08/11] [Locations history] Build list faster on start + display street names --- Space_Mapper/lib/models/list_view.dart | 30 ++++------- Space_Mapper/lib/ui/list_view.dart | 70 +++++++++++++++----------- 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/list_view.dart index 34513b0e..c2d41d8f 100644 --- a/Space_Mapper/lib/models/list_view.dart +++ b/Space_Mapper/lib/models/list_view.dart @@ -7,30 +7,10 @@ class CustomLocationsManager { } class CustomLocation { - CustomLocation( - {String? locality, - String? subAdministrativeArea, - // ignore: non_constant_identifier_names - String? ISOCountry, - String? timestamp, - String? activity, - num? speed, - num? speedAccuracy, - num? altitude, - num? altitudeAccuracy}) { - if (locality != null) setLocality(locality); - if (subAdministrativeArea != null) - setSubAdministrativeArea(subAdministrativeArea); - if (ISOCountry != null) setISOCountry(ISOCountry); - if (timestamp != null) setTimestamp(timestamp); - if (activity != null) setActivity(activity); - if (speed != null && speedAccuracy != null) setSpeed(speed, speedAccuracy); - if (altitude != null && altitudeAccuracy != null) - setAltitude(altitude, altitudeAccuracy); - } late final String _uuid; late String _locality = ""; late String _subAdministrativeArea = ""; + late String _street = ""; // ignore: non_constant_identifier_names late String _ISOCountry = ""; // 2 letter code late String _timestamp = ""; // ex: 2021-10-25T21:25:08.210Z @@ -85,6 +65,10 @@ class CustomLocation { _subAdministrativeArea = subAdminArea; } + void setStreet(String street) { + _street = street; + } + void setISOCountry(String iso) { _ISOCountry = iso; } @@ -120,6 +104,10 @@ class CustomLocation { return _subAdministrativeArea; } + String getStreet() { + return _street; + } + String getISOCountryCode() { return _ISOCountry; } diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index c7af1115..83c47662 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -35,41 +35,18 @@ void createCustomLocation(var recordedLocation, Placemark? placemark) { if (placemark != null) { String? locality = placemark.locality; String? subAdminArea = placemark.subAdministrativeArea; + String? street = placemark.street; + if (street != null) street += ", ${placemark.name}"; // ignore: non_constant_identifier_names String? ISO = placemark.isoCountryCode; location.setLocality(locality!); location.setSubAdministrativeArea(subAdminArea!); + location.setStreet(street!); location.setISOCountry(ISO!); } } -Future recalculateLocations() async { - List recordedLocations = await bg.BackgroundGeolocation.locations; - int recordedLocationsSize = recordedLocations.length; - List customLocations = CustomLocationsManager.fetchAll(); - - /// We check if there are new location entries that we haven't saved in our list - if (recordedLocationsSize != customLocations.length) { - for (int i = 0; i < recordedLocationsSize; ++i) { - //for (int i = 0; i < 10; ++i) { - //TODO: This is a mock to delete - // TODO: This nested 'for' has a complexity of O(n^2), we could make it more efficient - for (int j = 0; j < customLocations.length; ++j) { - if (recordedLocations[i]['uuid'] == - CustomLocationsManager.customLocations[j].getUUID()) continue; - } - //Match not found, we add the location - createCustomLocation( - recordedLocations[i], - await getLocationData(recordedLocations[i]['coords']['latitude'], - recordedLocations[i]['coords']['longitude'])); - } - //We update the state to display the new locations - - } -} - class STOListView extends StatefulWidget { const STOListView({Key? key}) : super(key: key); @@ -80,23 +57,56 @@ class STOListView extends StatefulWidget { class _STOListViewState extends State { late List items = CustomLocationsManager.fetchAll(); - Future _recalculateLocations() async { + /*Future _recalculateLocations() async { await recalculateLocations(); setState(() { items = CustomLocationsManager.fetchAll(); }); + }*/ + + Future recalculateLocations() async { + List recordedLocations = await bg.BackgroundGeolocation.locations; + int recordedLocationsSize = recordedLocations.length; + List customLocations = CustomLocationsManager.fetchAll(); + + /// We check if there are new location entries that we haven't saved in our list + if (recordedLocationsSize != customLocations.length) { + for (int i = 0; i < recordedLocationsSize; ++i) { + //for (int i = 0; i < 10; ++i) { + //TODO: This is a mock to delete + // TODO: This nested 'for' has a complexity of O(n^2), we could make it more efficient + for (int j = 0; j < customLocations.length; ++j) { + if (recordedLocations[i]['uuid'] == + CustomLocationsManager.customLocations[j].getUUID()) continue; + } + //Match not found, we add the location + createCustomLocation( + recordedLocations[i], + await getLocationData(recordedLocations[i]['coords']['latitude'], + recordedLocations[i]['coords']['longitude'])); + + //We update the state to display the new locations + if (this + .mounted) // We check if this screen is active. If we do 'setState' while it's not active, it'll crash (throw exception) + { + setState(() { + items = CustomLocationsManager.fetchAll(); + }); + } + } + } } @override void initState() { super.initState(); - _recalculateLocations(); + recalculateLocations(); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text("Locations History v1")), + appBar: AppBar(title: Text("Locations History")), body: ListView.builder( itemCount: items.length, itemBuilder: (context, index) { @@ -107,7 +117,7 @@ class _STOListViewState extends State { thisLocation.getSubAdministrativeArea() + ", " + thisLocation.getISOCountryCode(), - thisLocation.getTimestamp(), + thisLocation.getTimestamp() + "\n" + thisLocation.getStreet(), thisLocation.displayCustomText(10.0, 10.0), Icons.gps_fixed); }, From 851702bef9bce601432cec3959110f176f256cce Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Thu, 28 Oct 2021 02:28:06 +0200 Subject: [PATCH 09/11] [List View] Rewrite existing unit tests to adapt them to new code --- Space_Mapper/lib/ui/list_view.dart | 18 ----- Space_Mapper/test/unit/list_view_test.dart | 89 +++++++++------------- 2 files changed, 36 insertions(+), 71 deletions(-) diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index 83c47662..1ba96068 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -124,24 +124,6 @@ class _STOListViewState extends State { )); } - ListView _jobsListView(data) { - return ListView.builder( - itemCount: data.length, - itemBuilder: (context, index) { - CustomLocation thisLocation = data[index]; - return _tile( - thisLocation.getUUID().toString() + - thisLocation.getLocality() + - ", " + - thisLocation.getSubAdministrativeArea() + - ", " + - thisLocation.getISOCountryCode(), - thisLocation.getTimestamp(), - thisLocation.displayCustomText(10.0, 10.0), - Icons.gps_fixed); - }); - } - ListTile _tile(String title, String subtitle, String text, IconData icon) => ListTile( title: Text(title, diff --git a/Space_Mapper/test/unit/list_view_test.dart b/Space_Mapper/test/unit/list_view_test.dart index 2efd9bae..6e455502 100644 --- a/Space_Mapper/test/unit/list_view_test.dart +++ b/Space_Mapper/test/unit/list_view_test.dart @@ -1,113 +1,96 @@ -//import 'package:asm/models/list_view.dart'; +import '../../lib/models/list_view.dart'; import 'package:test/test.dart'; void main() { group('Locations History Screen - Unit Tests', () { - /*group('DisplayLocation class', () { + group('DisplayLocation class', () { group('formatTimestamp function', () { test('formatTimestamp: input a correct timestamp', () { String timestamp = "2021-10-25T21:25:08.210Z"; - DisplayLocation dL = - new DisplayLocation("", "", "", timestamp, "", 0, 0, 0, 0); - String ret = dL.formatTimestamp(dL.timestamp); + CustomLocation dL = new CustomLocation(); + dL.setTimestamp(timestamp); + String ret = dL.formatTimestamp(dL.getTimestamp()); expect(ret, "2021-10-25 | 21:25:08"); }); }); group('displayCustomText function', () { test('displayCustomText: Result when all data is valid', () { - String activity = "walking"; - num speed = 5; - num speedAccuracy = 1; - num altitude = 315; - num altitudeAccuracy = 3; + CustomLocation location = new CustomLocation(); + location.setActivity("walking"); + location.setSpeed(5, 1); + location.setAltitude(315, 3); num maxSpeedAcc = 5; num maxAltitudeAcc = 5; - DisplayLocation dL = new DisplayLocation("", "", "", "", activity, - speed, speedAccuracy, altitude, altitudeAccuracy); - - String ret = dL.displayCustomText(maxSpeedAcc, maxAltitudeAcc); + String ret = location.displayCustomText(maxSpeedAcc, maxAltitudeAcc); expect( ret, equals(" \nActivity: " + - activity + + location.getActivity() + " \nSpeed: " + - speed.toString() + + location.getSpeed().toString() + " m/s" + "\nAltitude: " + - altitude.toString() + + location.getAltitude().toString() + " m")); }); test('displayCustomText: Result when altitude in not accurate', () { - String activity = "walking"; - num speed = 1; - num speedAccuracy = 1; - num altitude = 5; - num altitudeAccuracy = - 50; //Is higher than max allowed altitude, so it should not print altitude + CustomLocation location = new CustomLocation(); + location.setActivity("walking"); + location.setSpeed(1, 1); + location.setAltitude(5, + 50); //It'ss higher than max allowed altitude, so it should not print altitude num maxSpeedAcc = 5; num maxAltitudeAcc = 10; - DisplayLocation dL = new DisplayLocation("", "", "", "", activity, - speed, speedAccuracy, altitude, altitudeAccuracy); - - String ret = dL.displayCustomText(maxSpeedAcc, maxAltitudeAcc); + String ret = location.displayCustomText(maxSpeedAcc, maxAltitudeAcc); expect( ret, equals(" \nActivity: " + - activity + + location.getActivity() + " \nSpeed: " + - speed.toString() + + location.getSpeed().toString() + " m/s")); }); test( 'displayCustomText: Result when speed and speedAccuracy is -1 (gps not used)', () { - String activity = "walking"; - num speed = -1; - num speedAccuracy = -1; - num altitude = 5; - num altitudeAccuracy = 10; + CustomLocation location = new CustomLocation(); + location.setActivity("walking"); + location.setSpeed(-1, -1); + location.setAltitude(5, 10); num maxSpeedAcc = 5; num maxAltitudeAcc = 10; - DisplayLocation dL = new DisplayLocation("", "", "", "", activity, - speed, speedAccuracy, altitude, altitudeAccuracy); - - String ret = dL.displayCustomText(maxSpeedAcc, maxAltitudeAcc); + String ret = location.displayCustomText(maxSpeedAcc, maxAltitudeAcc); expect( ret, equals(" \nActivity: " + - activity + + location.getActivity() + "\nAltitude: " + - altitude.toString() + + location.getAltitude().toString() + " m")); }); test( 'displayCustomText: Result when both speed and altitude are invalid', () { - String activity = "walking"; - num speed = -1; //Invalid input, should not print speed - num speedAccuracy = - -1; //Invalid input. But anyway this numbers doesn't matter, because speed is already invalid - num altitude = 5; - num altitudeAccuracy = - 50; //Is higher than max allowed altitude, so it should not print altitude + CustomLocation location = new CustomLocation(); + location.setActivity("walking"); + location.setSpeed(-1, -1); //Invalid input, should not print speed + location.setAltitude(5, + 50); //Accuracy is higher than max allowed altitude, so it should not print altitude num maxSpeedAcc = 5; num maxAltitudeAcc = 10; - DisplayLocation dL = new DisplayLocation("", "", "", "", activity, - speed, speedAccuracy, altitude, altitudeAccuracy); - - String ret = dL.displayCustomText(maxSpeedAcc, maxAltitudeAcc); - expect(ret, equals(" \nActivity: " + activity)); + String ret = location.displayCustomText(maxSpeedAcc, maxAltitudeAcc); + expect(ret, equals(" \nActivity: " + location.getActivity())); }); }); - });*/ + }); }); } From dd2bb46477fd1dc741a10b3ac1fb75f5ca5071af Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Thu, 28 Oct 2021 20:21:32 +0200 Subject: [PATCH 10/11] [Locations History] Sort by newest --- Space_Mapper/lib/models/list_view.dart | 24 +++++++++++++++---- Space_Mapper/lib/ui/list_view.dart | 32 +++++++++++--------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/list_view.dart index c2d41d8f..4ff63d77 100644 --- a/Space_Mapper/lib/models/list_view.dart +++ b/Space_Mapper/lib/models/list_view.dart @@ -1,8 +1,24 @@ +import 'package:collection/collection.dart'; + class CustomLocationsManager { static List customLocations = []; - static List fetchAll() { - return CustomLocationsManager.customLocations; + static List fetchAll({required bool sortByNewest}) { + if (sortByNewest) { + return customLocations; + } else { + return new List.from(customLocations.reversed); + } + } + + static CustomLocation? fetchByUUID(String uuid) { + CustomLocation? ret = customLocations + .firstWhereOrNull((element) => element.getUUID() == uuid); + return ret; + } + + static RemoveByUUID(String uuid) { + customLocations.removeWhere((element) => element.getUUID() == uuid); } } @@ -52,7 +68,7 @@ class CustomLocation { return ret; } - /// Variable setters + // Variable setters void setUUID(String uuid) { _uuid = uuid; } @@ -91,7 +107,7 @@ class CustomLocation { _altitudeAccuracy = altitudeAcc; } - /// Variable getters + // Variable getters String getUUID() { return _uuid; } diff --git a/Space_Mapper/lib/ui/list_view.dart b/Space_Mapper/lib/ui/list_view.dart index 1ba96068..611c5e48 100644 --- a/Space_Mapper/lib/ui/list_view.dart +++ b/Space_Mapper/lib/ui/list_view.dart @@ -55,27 +55,16 @@ class STOListView extends StatefulWidget { } class _STOListViewState extends State { - late List items = CustomLocationsManager.fetchAll(); - - /*Future _recalculateLocations() async { - await recalculateLocations(); - setState(() { - items = CustomLocationsManager.fetchAll(); - }); - }*/ + late List customLocations = + CustomLocationsManager.fetchAll(sortByNewest: true); Future recalculateLocations() async { List recordedLocations = await bg.BackgroundGeolocation.locations; - int recordedLocationsSize = recordedLocations.length; - List customLocations = CustomLocationsManager.fetchAll(); /// We check if there are new location entries that we haven't saved in our list - if (recordedLocationsSize != customLocations.length) { - for (int i = 0; i < recordedLocationsSize; ++i) { - //for (int i = 0; i < 10; ++i) { - //TODO: This is a mock to delete - // TODO: This nested 'for' has a complexity of O(n^2), we could make it more efficient - for (int j = 0; j < customLocations.length; ++j) { + if (recordedLocations.length != customLocations.length) { + for (int i = recordedLocations.length - 1; i >= 0; --i) { + for (int j = customLocations.length - 1; j >= 0; --j) { if (recordedLocations[i]['uuid'] == CustomLocationsManager.customLocations[j].getUUID()) continue; } @@ -90,7 +79,12 @@ class _STOListViewState extends State { .mounted) // We check if this screen is active. If we do 'setState' while it's not active, it'll crash (throw exception) { setState(() { - items = CustomLocationsManager.fetchAll(); + customLocations = + CustomLocationsManager.fetchAll(sortByNewest: true); + print("Loading positions: " + + customLocations.length.toString() + + " out of " + + recordedLocations.length.toString()); }); } } @@ -108,9 +102,9 @@ class _STOListViewState extends State { return Scaffold( appBar: AppBar(title: Text("Locations History")), body: ListView.builder( - itemCount: items.length, + itemCount: customLocations.length, itemBuilder: (context, index) { - CustomLocation thisLocation = items[index]; + CustomLocation thisLocation = customLocations[index]; return _tile( thisLocation.getLocality() + ", " + From 62057456335b3fe981ecdad753464d862f068e2d Mon Sep 17 00:00:00 2001 From: Pablo Galve Date: Fri, 29 Oct 2021 02:08:18 +0200 Subject: [PATCH 11/11] [Location History] Write more unit tests for CustomLocations --- Space_Mapper/lib/models/list_view.dart | 4 -- Space_Mapper/pubspec.lock | 7 +++ Space_Mapper/pubspec.yaml | 3 +- Space_Mapper/test/unit/list_view_test.dart | 62 +++++++++++++++++++++- 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/Space_Mapper/lib/models/list_view.dart b/Space_Mapper/lib/models/list_view.dart index 4ff63d77..fcf4621a 100644 --- a/Space_Mapper/lib/models/list_view.dart +++ b/Space_Mapper/lib/models/list_view.dart @@ -16,10 +16,6 @@ class CustomLocationsManager { .firstWhereOrNull((element) => element.getUUID() == uuid); return ret; } - - static RemoveByUUID(String uuid) { - customLocations.removeWhere((element) => element.getUUID() == uuid); - } } class CustomLocation { diff --git a/Space_Mapper/pubspec.lock b/Space_Mapper/pubspec.lock index cd907b31..650ebcc6 100644 --- a/Space_Mapper/pubspec.lock +++ b/Space_Mapper/pubspec.lock @@ -148,6 +148,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + faker: + dependency: "direct main" + description: + name: faker + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" ffi: dependency: transitive description: diff --git a/Space_Mapper/pubspec.yaml b/Space_Mapper/pubspec.yaml index d962915b..7ffa749a 100644 --- a/Space_Mapper/pubspec.yaml +++ b/Space_Mapper/pubspec.yaml @@ -35,7 +35,8 @@ dependencies: url_launcher: ^6.0.12 mailto: ^2.0.0 geocoding: ^2.0.1 - + faker: ^2.0.0 + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.3 diff --git a/Space_Mapper/test/unit/list_view_test.dart b/Space_Mapper/test/unit/list_view_test.dart index 6e455502..fd7e0245 100644 --- a/Space_Mapper/test/unit/list_view_test.dart +++ b/Space_Mapper/test/unit/list_view_test.dart @@ -1,9 +1,10 @@ -import '../../lib/models/list_view.dart'; +import 'package:asm/models/list_view.dart'; import 'package:test/test.dart'; +import 'package:faker/faker.dart'; void main() { group('Locations History Screen - Unit Tests', () { - group('DisplayLocation class', () { + group('CustomLocation class', () { group('formatTimestamp function', () { test('formatTimestamp: input a correct timestamp', () { String timestamp = "2021-10-25T21:25:08.210Z"; @@ -92,5 +93,62 @@ void main() { }); }); }); + group("CustomLocationsManager class", () { + test("fetchAll and fetchByUUID", () async { + //We create 50 fake locations to do the test + for (int i = 0; i < 50; i++) { + CustomLocation location = new CustomLocation(); + location.setUUID(faker.guid.guid()); + location.setActivity("walk"); + location.setAltitude( + faker.randomGenerator + .numbers(8848, 1)[0], //8848 metres is Mount Everest height + faker.randomGenerator.numbers(300, 1)[0]); + location.setSpeed(faker.randomGenerator.numbers(600, 1)[0], + faker.randomGenerator.numbers(300, 1)[0]); + location.setISOCountry(faker.address.countryCode()); + location.setLocality(faker.address.city()); + location.setStreet(faker.address.streetAddress()); + location.setSubAdministrativeArea(faker.address.state()); + location.setTimestamp(faker.date.random.toString()); + CustomLocationsManager.customLocations.add(location); + } + + List locations = + CustomLocationsManager.fetchAll(sortByNewest: true); + for (var location in locations) { + expect(location.getUUID(), isNotEmpty); + + CustomLocation? fetchedLocation = + CustomLocationsManager.fetchByUUID(location.getUUID()); + + expect(fetchedLocation, + isNotNull); // Not null because we get the UUID from the list, so it must exist + + if (fetchedLocation != null) { + // Test that every fetched location is exactly equal as the current location + expect(fetchedLocation.getUUID(), equals(location.getUUID())); + expect( + fetchedLocation.getActivity(), equals(location.getActivity())); + expect( + fetchedLocation.getAltitude(), equals(location.getAltitude())); + expect(fetchedLocation.getAltitudeAcc(), + equals(location.getAltitudeAcc())); + expect(fetchedLocation.getISOCountryCode(), + equals(location.getISOCountryCode())); + expect( + fetchedLocation.getLocality(), equals(location.getLocality())); + expect(fetchedLocation.getSpeed(), equals(location.getSpeed())); + expect( + fetchedLocation.getSpeedAcc(), equals(location.getSpeedAcc())); + expect(fetchedLocation.getStreet(), equals(location.getStreet())); + expect(fetchedLocation.getSubAdministrativeArea(), + equals(location.getSubAdministrativeArea())); + expect(fetchedLocation.getTimestamp(), + equals(location.getTimestamp())); + } + } + }); + }); }); }