Commit 4b4c7ab2 authored by Nicolas Richard Walter Boeckh's avatar Nicolas Richard Walter Boeckh 💬

Removed todos, Integrated prefs, added network monitoring

parent cd8ba37a
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"app_settings","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\app_settings-4.0.3\\\\","dependencies":[]},{"name":"connectivity","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\connectivity-0.4.9+2\\\\","dependencies":[]},{"name":"flutter_blue","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"geolocator","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\geolocator-5.2.1\\\\","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_api_availability-2.0.2\\\\","dependencies":[]},{"name":"location_permissions","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location_permissions-2.0.4+1\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-1.6.17\\\\","dependencies":[]},{"name":"permission_handler","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler-4.2.0+hotfix.3\\\\","dependencies":[]},{"name":"shared_preferences","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences-0.5.7+2\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"android":[{"name":"app_settings","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\app_settings-4.0.3\\\\","dependencies":[]},{"name":"connectivity","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\connectivity-0.4.9+2\\\\","dependencies":[]},{"name":"flutter_blue","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"geolocator","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\geolocator-5.2.1\\\\","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_api_availability-2.0.2\\\\","dependencies":[]},{"name":"location_permissions","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location_permissions-2.0.4+1\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-1.6.17\\\\","dependencies":[]},{"name":"permission_handler","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler-4.2.0+hotfix.3\\\\","dependencies":[]},{"name":"shared_preferences","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences-0.5.7+2\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"macos":[{"name":"connectivity_macos","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\connectivity_macos-0.1.0+4\\\\","dependencies":[]},{"name":"path_provider_macos","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_macos-0.0.4+4\\\\","dependencies":[]},{"name":"shared_preferences_macos","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_macos-0.0.1+8\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_linux-0.0.1+2\\\\","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_windows-0.0.4+1\\\\","dependencies":[]}],"web":[{"name":"connectivity_for_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\connectivity_for_web-0.3.1+2\\\\","dependencies":[]},{"name":"shared_preferences_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_web-0.1.2+5\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"app_settings","dependencies":[]},{"name":"connectivity","dependencies":["connectivity_macos","connectivity_for_web"]},{"name":"connectivity_for_web","dependencies":[]},{"name":"connectivity_macos","dependencies":[]},{"name":"flutter_blue","dependencies":[]},{"name":"geolocator","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","dependencies":[]},{"name":"location_permissions","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2020-09-27 02:00:26.008342","version":"1.21.0-10.0.pre.193"}
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"app_settings","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\app_settings-4.0.3\\\\","dependencies":[]},{"name":"connectivity","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\connectivity-0.4.9+2\\\\","dependencies":[]},{"name":"flutter_blue","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"geolocator","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\geolocator-5.2.1\\\\","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_api_availability-2.0.2\\\\","dependencies":[]},{"name":"location_permissions","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location_permissions-2.0.4+1\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-1.6.17\\\\","dependencies":[]},{"name":"permission_handler","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler-4.2.0+hotfix.3\\\\","dependencies":[]},{"name":"shared_preferences","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences-0.5.7+2\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"android":[{"name":"app_settings","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\app_settings-4.0.3\\\\","dependencies":[]},{"name":"connectivity","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\connectivity-0.4.9+2\\\\","dependencies":[]},{"name":"flutter_blue","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"geolocator","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\geolocator-5.2.1\\\\","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_api_availability-2.0.2\\\\","dependencies":[]},{"name":"location_permissions","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location_permissions-2.0.4+1\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-1.6.17\\\\","dependencies":[]},{"name":"permission_handler","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler-4.2.0+hotfix.3\\\\","dependencies":[]},{"name":"shared_preferences","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences-0.5.7+2\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"macos":[{"name":"connectivity_macos","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\connectivity_macos-0.1.0+4\\\\","dependencies":[]},{"name":"path_provider_macos","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_macos-0.0.4+4\\\\","dependencies":[]},{"name":"shared_preferences_macos","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_macos-0.0.1+8\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_linux-0.0.1+2\\\\","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_windows-0.0.4+1\\\\","dependencies":[]}],"web":[{"name":"connectivity_for_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\connectivity_for_web-0.3.1+2\\\\","dependencies":[]},{"name":"shared_preferences_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_web-0.1.2+5\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"app_settings","dependencies":[]},{"name":"connectivity","dependencies":["connectivity_macos","connectivity_for_web"]},{"name":"connectivity_for_web","dependencies":[]},{"name":"connectivity_macos","dependencies":[]},{"name":"flutter_blue","dependencies":[]},{"name":"geolocator","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","dependencies":[]},{"name":"location_permissions","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2020-09-28 02:00:38.728340","version":"1.21.0-10.0.pre.193"}
\ No newline at end of file
......@@ -38,7 +38,6 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.logair.logair_application"
minSdkVersion 19
targetSdkVersion 28
......
......@@ -26,6 +26,7 @@ class MapDisplayController {
/// The [FlutterMap]'s zoom level, used to keep centering stable.
double _zoom = 13;
/// Retrieves [FlutterMap]'s current zoom level.
double get zoom => _zoom;
/// The [FlutterMap]s [MapController], kept unique in order to avoid incessant reloading of resources.
......@@ -37,8 +38,10 @@ class MapDisplayController {
/// [Marker] representing the user's current [Position].
Marker _marker;
/// Null [BehaviorSubject] tracking when the [FlutterMap] should be redrawn.
BehaviorSubject shouldRedraw = BehaviorSubject();
Future<void> _rebuildMarkers() async {
List<DataPacket> packets = await MainDatabaseHandler().getLastPackets(200);
packets.forEach((DataPacket packet) {
......@@ -63,12 +66,11 @@ class MapDisplayController {
List<CircleMarker> _pmMarkers = <CircleMarker>[];
/// [Singleton]() instantiation factory.
/// Singleton instantiation factory.
factory MapDisplayController() => _singleton;
final BehaviorSubject<Position> _positionObserver = PositionHandler().devicePosition;
/// @Getter for [_polylines].
List<Polyline> get polylines => this._polylines;
......@@ -105,7 +107,6 @@ class MapDisplayController {
}
/// This function is executed on first initialization of the [MapDisplayController]
// TODO: Throttle to 1 in 5 updates for efficiency ?
MapDisplayController._internal() {
/// A new [MapController] is used as the global Controller.
/// This avoids Garbage Collection rules and enables the [FlutterMap] to survive [State] changes, and other such events.
......@@ -135,12 +136,11 @@ class MapDisplayController {
this._controller.move(this._currentPos, this._zoom);
/// Move the [Marker] to the current [Position].
// TODO: Use StreamBuilder in Map.
this._marker = this._buildLocationMarker(this._currentPos);
if (this._currentPos != this._lastPos)
this.shouldRedraw.value = null;
if ((this._lastPos != null && this._currentPos != null)) {// && this._distance.as(LengthUnit.Meter, this._lastPos, this._currentPos) >= 2)) {
if ((this._lastPos != null && this._currentPos != null) && this._distance.as(LengthUnit.Meter, this._lastPos, this._currentPos) >= .5) {
if (this._polylines.length == 0) {
this._polylines.add(
Polyline(
......@@ -183,6 +183,12 @@ class MapDisplayController {
});
}
@override
MapDisplayController.dispose() {
shouldRedraw.close();
_positionObserver.close();
}
/// Internal static [Singleton]() reference.
static final MapDisplayController _singleton = new MapDisplayController._internal();
......
import 'dart:async';
import 'dart:convert';
import 'dart:collection';
import 'dart:ffi';
import 'package:connectivity/connectivity.dart';
import 'package:flutter/cupertino.dart';
......@@ -9,7 +10,6 @@ import 'package:http/http.dart' as http;
import 'package:logair_application/logic/data_header.dart';
import 'package:logair_application/logic/data_packet.dart';
import 'package:logair_application/logic/handlers/data_handler.dart';
import 'package:logair_application/logic/handlers/main_database_handler.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
import 'package:logair_application/utils/enums/preference_keys.dart';
......@@ -24,6 +24,8 @@ class NetworkHandler {
/// [NetworkHandler] initializer that sets up listeners on the [PreferencesHandler].
NetworkHandler._internal() {
this._uploadBytes.value = 0; this._uploadCounter.value = 0; this._downloadCounter.value = 0; this._downloadBytes.value = 0;
this._pushFrequency.listen((value) {
if (value != this._oldPushFrequency) {
// Resets the timer if the value has changed.
......@@ -42,11 +44,15 @@ class NetworkHandler {
/// Retrieve the interval between attempts to send the data to the server. (default: 1 minute).
PreferencesHandler().getPreferencesInt(PreferenceKeys.NET__TIME_TO_PUSH.key).then((value) => this._pushFrequency.value = value);
/// Retrieve the interval between attempts to acquire Location of Interest data from the server. (default: 1 minute).
PreferencesHandler().getPreferencesInt(PreferenceKeys.LOI__ACK_FREQUENCY.key).then((value) => this._loiAckFrequency.value = value);
/// Checks the [PreferencesHandler] every 20 seconds on Preference updates.
new Timer.periodic(Duration(seconds: 20), (Timer t) async {
this._pushAmount.value = await PreferencesHandler().getPreferencesInt(PreferenceKeys.NET__MAX_ITEMS_PER_PUSH.key);
this._pushFrequency.value = await PreferencesHandler().getPreferencesInt(PreferenceKeys.NET__TIME_TO_PUSH.key);
this._loiAckFrequency.value = await PreferencesHandler().getPreferencesInt(PreferenceKeys.LOI__ACK_FREQUENCY.key);
});
}
......@@ -60,6 +66,12 @@ class NetworkHandler {
}
_pushAmount.close();
_pushFrequency.close();
_loiAckFrequency.close();
_uploadCounter.close();
_uploadBytes.close();
_downloadBytes.close();
_downloadCounter.close();
}
static final NetworkHandler _singleton = new NetworkHandler._internal();
......@@ -72,7 +84,15 @@ class NetworkHandler {
/// [BehaviorSubject] concerning the frequency of attempts to push to the server.
BehaviorSubject<int> _pushFrequency = BehaviorSubject<int>();
/// [BehaviorSubject] concerning the frequency of attempts to retrieve data from the server.
BehaviorSubject<int> _loiAckFrequency = BehaviorSubject<int>();
BehaviorSubject<int> _uploadCounter = BehaviorSubject<int>();
BehaviorSubject<int> _uploadBytes = BehaviorSubject<int>();
BehaviorSubject<int> _downloadBytes = BehaviorSubject<int>();
BehaviorSubject<int> _downloadCounter = BehaviorSubject<int>();
/// A reference to the old frequency in order to limit updates.
int _oldPushFrequency = -1;
......@@ -111,6 +131,8 @@ class NetworkHandler {
return;
}
this._uploadCounter.value += 1;
/// The map representation of a [DataHeader].
Map<String, dynamic> headerData = DataHeader().jsonify();
......@@ -130,13 +152,16 @@ class NetworkHandler {
postData.addAll(headerData);
postData.addAll(packetList);
String body = json.encode(postData);
this._uploadBytes.value += body.length;
/// Attempt to send the data to the endpoint via HTTP POST request.
http.Response response;
try {
response = await http.post(
url,
headers: { "accept": "application/json", "content-type": "application/json" },
body: json.encode(postData)
body: body
);
/// On error, the error should be ignored and not hang/break the thread.
} catch (e) {
......@@ -146,7 +171,7 @@ class NetworkHandler {
/// If the response is defined as an instance of [http.Response], check it's status code and remove all of the packets from the log.
if (response != null && response.statusCode == 200) {
/// If the transfer was successful ()
/// If the transfer was successful
await MainDatabaseHandler().setExported(databaseData.map((x) => x.item1).toList());
printDebug('NetworkHandler: RESPONSE ${response.statusCode}');
......@@ -154,14 +179,18 @@ class NetworkHandler {
}
}
// TODO Set user defined duration
// TODO Set user defined radius
/// Get all of the points in proximity to the device.
void getProximityPoints(String deviceId, LatLngBounds bounds) async {
if (!(await checkNetworkAllowed())) return;
this._downloadCounter.value += 1;
int age = await PreferencesHandler().getPreferencesInt(PreferenceKeys.MAP__PROX__MAX_AGE_SECONDS.key);
int limit = await PreferencesHandler().getPreferencesInt(PreferenceKeys.MAP__PROX__MAX_PER_QUERY.key);
/// The default endpoint.
String url = 'https://api.logair.unige.ch/v1/geo/bounds?device_id=$deviceId&lat1=${bounds.northEast.latitude}&lng1=${bounds.northEast.longitude}&lat2=${bounds.southWest.latitude}&lng2=${bounds.southWest.longitude}&age=${300000}&limit=${200}';
String url = 'https://api.logair.unige.ch/v1/geo/bounds?device_id=$deviceId&lat1=${bounds.northEast.latitude}&lng1=${bounds.northEast.longitude}&lat2=${bounds.southWest.latitude}&lng2=${bounds.southWest.longitude}&age=${age * 1000}&limit=$limit';
/// Attempt to send the data to the endpoint via HTTP POST request.
http.Response response;
try {
......@@ -169,6 +198,7 @@ class NetworkHandler {
url,
headers: { "accept": "application/json", "content-type": "application/json" },
);
this._downloadBytes.value += response.contentLength;
/// On error, the error should be ignored and not break the thread.
} catch (e) {
printDebug('NetworkHandler: ${e.toString()} COULDN\'T CONNECT');
......@@ -188,15 +218,15 @@ class NetworkHandler {
}
}
/// Retrieve the data for a specific [LocationOfInterest].
Future<http.Response> _getLocationOfInterestData({@required double latitude, @required double longitude}) async {
if (!(await checkNetworkAllowed())) return null;
this._downloadCounter.value += 1;
// TODO retrieve from preferences.
double focus = 0.0001;
int age = 60000;
double focus = (await PreferencesHandler().getPreferencesInt(PreferenceKeys.LOI__FOCUS.key) ?? 1) / 10000;
int age = (await PreferencesHandler().getPreferencesInt(PreferenceKeys.LOI__AGE.key) ?? 300) * 1000;
/// The default endpoint.
// TODO get focus from preferences
String url = 'https://api.logair.unige.ch/v1/geo/proximity?lat=$latitude&lng=$longitude&age=$age&focus=$focus';
/// Attempt to send the data to the endpoint via HTTP POST request.
http.Response response;
......@@ -205,21 +235,24 @@ class NetworkHandler {
url,
headers: { "accept": "application/json", "content-type": "application/json" },
);
this._downloadBytes.value += response.contentLength;
/// On error, the error should be ignored and not break the thread.
} catch (e) {
printDebug('NetworkHandler: ${e.toString()} COULDN\'T CONNECT');
response = null;
}
return response;
}
/// Retrieve the average [DataPacket] representation of data collected in a user-defined [LocationOfInterest].
Stream<DataPacket> generateLocationOfInterestStream({@required LocationOfInterest loi}) async* {
while (true) {
this._downloadCounter.value += 1;
http.Response response;
try {
response = await _getLocationOfInterestData(latitude: loi.latitude, longitude: loi.longitude);
this._downloadBytes.value += response.contentLength;
} catch (e) {
printDebug('NetworkHandler: ${e.toString()} COULDN\'T CONNECT');
yield null;
......@@ -229,21 +262,27 @@ class NetworkHandler {
if (response != null && response.statusCode == 200) {
Map<String, dynamic> responseBody = json.decode(response.body);
packet = DataPacket.fromData(
0,
0,
loi.latitude, loi.longitude, 0, 0, 0,
castDoubleWrapper(responseBody['avg_temperature']), castDoubleWrapper(responseBody['avg_pressure']), castDoubleWrapper(responseBody['avg_relative_humidity']),
castDoubleWrapper(responseBody['avg_pm_1']), castDoubleWrapper(responseBody['avg_pm_2_5']), castDoubleWrapper(responseBody['avg_pm_4']), castDoubleWrapper(responseBody['avg_pm_10']),
''
);
yield packet;
} else
yield null;
}
// TODO: Preferences.
await Future.delayed(Duration(seconds: 60));
yield packet;
await Future.delayed(Duration(seconds: _loiAckFrequency.value ?? 60));
}
}
// Upload counter // Upload Rate (kb/s) // Download rate (kb/s) // Download counter
Stream<Tuple4<int, int, int, int>> networkUsageStream() async* {
while (true) {
yield Tuple4(this._uploadBytes.value ?? 0, this._uploadCounter.value ?? 0, this._downloadCounter.value ?? 0, this._downloadBytes.value ?? 0);
this._uploadBytes.value = 0; this._uploadCounter.value = 0; this._downloadCounter.value = 0; this._downloadBytes.value = 0;
await Future.delayed(Duration(seconds: 1));
}
}
}
\ No newline at end of file
......@@ -5,7 +5,6 @@ import 'package:logair_application/utils/enums/pm_symbol.dart';
import 'package:logair_application/utils/helpers/location_of_interest.dart';
import 'package:logair_application/utils/utils.dart';
//TODO Add weather ? (Not feasible unless negotiate free API access)
/// Widget that displays the data specific to a specific place, depending on the stream provided.
/// Data displayed is PM1, PM2.5, PM4, PM10, Temperature, Pressure, Relative Humidity.
///
......
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong/latlong.dart';
import 'package:logair_application/logic/controllers/map_display_controller.dart';
class MapWidget extends StatefulWidget {
......@@ -47,13 +46,4 @@ class _MapWidgetState extends State<MapWidget> {
],
);
}
Marker _buildLocationMarker(LatLng position) => Marker(
width: 20.0,
height: 20.0,
point: position,
builder: (ctx) => Container(
child: Icon(Icons.location_on, color: Colors.red,),
),
);
}
\ No newline at end of file
......@@ -2,7 +2,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:logair_application/logic/controllers/map_display_controller.dart';
import 'package:logair_application/ui/components/body/map/map.dart';
import 'package:logair_application/ui/components/common/base_widget.dart';
......
......@@ -5,7 +5,6 @@ import 'package:logair_application/ui/components/body/overview/overview_widgets/
import 'package:logair_application/ui/components/body/overview/overview_widgets/map_position_overview_widget.dart';
import 'package:logair_application/ui/components/body/overview/overview_widgets/network_overview_widget.dart';
import 'package:logair_application/ui/components/body/overview/overview_navigation_button.dart';
import 'package:logair_application/ui/components/body/bluetooth/bluetooth_selection.dart';
import 'package:logair_application/ui/components/footer/main_footer.dart';
import 'package:logair_application/ui/routes/preferences.dart';
......
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
......
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fluttericon/linecons_icons.dart';
......
......@@ -10,15 +10,6 @@ import 'package:tuple/tuple.dart';
class NetworkOverviewWidget extends StatelessWidget {
NetworkOverviewWidget({Key key}) : super(key: key);
// Upload counter // Upload Rate (kb/s) // Download rate (kb/s) // Download counter
Stream<Tuple4<int, int, int, int>> mockStream() async* {
while (true) {
yield Tuple4(1, 340, 130, 0);
await Future.delayed(Duration(milliseconds: 500));
}
}
/// Builds a [Text] containing the time at which the device last pushed data to the server.
StreamBuilder<DateTime> _buildLastConnectionWidget() {
return StreamBuilder<DateTime>(
......@@ -36,14 +27,14 @@ class NetworkOverviewWidget extends StatelessWidget {
/// Builds a [Row] containing the upload and download speeds.
StreamBuilder<Tuple4<int, int, int, int>> _buildConnectionUsageWidget() {
return StreamBuilder<Tuple4<int, int, int, int>>(
stream: mockStream(),
stream: NetworkHandler().networkUsageStream(),
initialData: Tuple4(0, 0, 0, 0),
builder: (BuildContext context, AsyncSnapshot snapshot) {
Tuple4<int, int, int, int> data = snapshot.data;
int uploadCounter = data.item1;
int uploadRate = data.item2;
int downloadRate = data.item3;
int downloadCounter = data.item4;
int uploadRate = data.item1;
int uploadCounter = data.item2;
int downloadCounter = data.item3;
int downloadRate = data.item4;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
......
......@@ -59,12 +59,14 @@ class _PreferencesWidgetState extends State<PreferencesWidget> {
final TextEditingController _loiFocusController = TextEditingController();
final TextEditingController _loiAgeController = TextEditingController();
final TextEditingController _loiAckController = TextEditingController();
final TextEditingController _bleServiceController = TextEditingController();
final TextEditingController _bleCharacteristicController = TextEditingController();
final TextEditingController _mapQueryLimitController = TextEditingController();
final TextEditingController _mapQueryAgeController = TextEditingController();
final TextEditingController _mapAckController = TextEditingController();
@override
......@@ -81,11 +83,16 @@ class _PreferencesWidgetState extends State<PreferencesWidget> {
_gpsAccuracyController.close();
_gpsIntervalController.dispose();
_loiFocusController.dispose();
_loiAgeController.dispose();
_loiAckController.dispose();
_bleServiceController.dispose();
_bleCharacteristicController.dispose();
_mapQueryLimitController.dispose();
_mapQueryAgeController.dispose();
_mapAckController.dispose();
super.dispose();
}
......@@ -223,6 +230,14 @@ class _PreferencesWidgetState extends State<PreferencesWidget> {
inputFormatters: <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ],
validation: RegExp(r'\d+')
),
TextInputTile(
controller: _loiAckController,
labelText: 'Frequency of data acquisition',
defaultText: 'Unit: Seconds | Suggested: 60',
prefsKey: PreferenceKeys.LOI__ACK_FREQUENCY.key,
inputFormatters: <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ],
validation: RegExp(r'\d+')
),
]
),
// BLUETOOTH
......@@ -277,6 +292,14 @@ class _PreferencesWidgetState extends State<PreferencesWidget> {
subtitle: '(Advanced)',
icon: Icons.map,
children: [
TextInputTile(
controller: this._mapAckController,
labelText: 'Frequency of data acquisition',
defaultText: 'Unit: Seconds | Suggested: 60',
prefsKey: PreferenceKeys.MAP__PROX__ACK.key,
inputFormatters: <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ],
validation: RegExp(r'\d+'),
),
CheckboxListTile(
title: Text('Modify advanced map settings'),
value: this._advancedMapOptions ?? false,
......@@ -371,11 +394,17 @@ class _PreferencesWidgetState extends State<PreferencesWidget> {
await PreferencesHandler().setPreferencesInt(PreferenceKeys.DB__MAX_AGE.key, int.parse(_dbMaxAgeController.text));
await PreferencesHandler().setPreferencesInt(PreferenceKeys.GPS__ACCURACY.key, _gpsAccuracyController.value);
await PreferencesHandler().setPreferencesInt(PreferenceKeys.GPS__INTERVAL_SECONDS.key, int.parse(_gpsIntervalController.text));
await PreferencesHandler().setPreferencesInt(PreferenceKeys.LOI__FOCUS.key, int.parse(_loiFocusController.text));
await PreferencesHandler().setPreferencesInt(PreferenceKeys.LOI__AGE.key, int.parse(_loiAgeController.text));
await PreferencesHandler().setPreferencesInt(PreferenceKeys.LOI__ACK_FREQUENCY.key, int.parse(_loiAckController.text));
await PreferencesHandler().setPreferencesBool(PreferenceKeys.BT__USING_ADVANCED.key, _advancedBTOptions);
await PreferencesHandler().setPreferencesString(PreferenceKeys.BT__SERVICE_UUID.key, _bleServiceController.text);
await PreferencesHandler().setPreferencesString(PreferenceKeys.BT__CHARACTERISTIC_UUID.key, _bleCharacteristicController.text);
await PreferencesHandler().setPreferencesInt(PreferenceKeys.MAP__PROX__ACK.key, int.parse(_mapAckController.text));
await PreferencesHandler().setPreferencesBool(PreferenceKeys.MAP__USING_ADVANCED.key, _advancedMapOptions);
await PreferencesHandler().setPreferencesInt(PreferenceKeys.MAP__PROX__MAX_PER_QUERY.key, int.parse(_mapQueryLimitController.text));
await PreferencesHandler().setPreferencesInt(PreferenceKeys.MAP__PROX__MAX_AGE_SECONDS.key, int.parse(_mapQueryAgeController.text));
......
......@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:logair_application/ui/components/header/bluetooth_button.dart';
import 'package:logair_application/ui/components/header/main_header_components/database_status.dart';
import 'package:logair_application/ui/components/header/main_header_components/device_battery.dart';
import 'package:logair_application/ui/components/header/main_header_components/network_status.dart';
/// [Widget] regrouping certain status elements from the overview pane, for quick lookup.
///
......@@ -41,9 +42,7 @@ class MainHeader extends StatelessWidget {
child : Row(
children: [
Expanded(
child: _container(Icon(
Icons.network_check,size: IconTheme.of(context).size,
))
child: _container(NetworkStatusWidget())
),
Expanded(
child: _container(Icon(
......
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:logair_application/logic/handlers/network_handler.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:tuple/tuple.dart';
class NetworkStatusWidget extends StatelessWidget {
/// Builds a [Row] containing the upload and download speeds.
StreamBuilder<Tuple4<int, int, int, int>> _buildConnectionUsageWidget() {
return StreamBuilder<Tuple4<int, int, int, int>>(
stream: NetworkHandler().networkUsageStream(),
initialData: Tuple4(0, 0, 0, 0),
builder: (BuildContext context, AsyncSnapshot snapshot) {
Tuple4<int, int, int, int> data = snapshot.data;
int uploadRate = data.item1;
int uploadCounter = data.item2;
int downloadCounter = data.item3;
int downloadRate = data.item4;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 4,
),
Expanded(
child: Text(
'${NumberFormat.compact().format(uploadRate)}\nkb/s',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.grey[800]
)
),
),
Container(
width: 30,
height: 35,
child: Stack(
children: [
Positioned(
top: 0,
child: Icon(
MdiIcons.arrowUpThick,
color: uploadCounter == 0 ? Colors.grey[800] : Colors.orange,
),
),
Positioned(
left: 4,
top: 12,
child: Icon(
MdiIcons.arrowDownThick,
color: downloadCounter == 0 ? Colors.grey[800] : Colors.green,
),
)
],
),
),
Expanded(
child: Text(
'${NumberFormat.compact().format(downloadRate)}\nkb/s',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.grey[800]
)
),
),
],
);
}
);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(Icons.network_check,size: IconTheme.of(context).size),
_buildConnectionUsageWidget()
],
);
}
}
\ No newline at end of file
......@@ -33,48 +33,52 @@ class PreferenceKeys {
/// Returns [_defaultValue].
dynamic get defaultValue => _defaultValue;
static const GEN__SHARE
static const GEN__SHARE // Should the app share it's data with LogAir's servers ?
= const PreferenceKeys._internal('GEN.SHARE', bool, false);
static const GEN__FIRST
static const GEN__FIRST // Is it the first time the app has been run ?
= const PreferenceKeys._internal('GEN.FIRST', bool, true);
static const NET__MAX_ITEMS_PER_PUSH
static const NET__MAX_ITEMS_PER_PUSH // How much data can the app upload at once ?
= const PreferenceKeys._internal('NET.MAX_ITEMS_PER_PUSH', int, 100);
static const NET__TIME_TO_PUSH