Commit 8d46dd31 authored by Nicolas Richard Walter Boeckh's avatar Nicolas Richard Walter Boeckh 💬

Merge branch 'preferences-integration' into 'main'

Preferences integration

See merge request !7
parents ce808319 fa66a876
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"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.0\\\\","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":"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.0\\\\","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":"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":[],"windows":[],"web":[{"name":"shared_preferences_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_web-0.1.2+5\\\\","dependencies":[]}]},"dependencyGraph":[{"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":[]},{"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-08-26 17:00:48.008588","version":"1.22.0-2.0.pre.18"}
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"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.0\\\\","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":"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.0\\\\","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":"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":[],"windows":[],"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":"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":[]},{"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-07 01:52:18.814120","version":"1.21.0-10.0.pre.193"}
\ No newline at end of file
......@@ -5,9 +5,22 @@ import 'package:logair_application/ui/routes/home.dart';
/// This controller class is integrated to the [HomeView] to enable out of scope control of select Widgets therein.
class HomeController {
/// The page that the controllers were previously pointing toward.
int _previousPage;
/// The page that the controllers are now pointing toward.
int _currentPage;
/// Whether the navbar triggered the action, to avoid livelock generated by [CircularBottomNavigationController] listening to [_navigationController.value].
bool _triggerOriginNavbar;
factory HomeController() => _singleton;
HomeController._internal();
HomeController._internal() {
this._currentPage = 0;
this._previousPage = 0;
this._triggerOriginNavbar = true;
}
static final HomeController _singleton = new HomeController._internal();
......@@ -21,17 +34,41 @@ class HomeController {
set carouselController(PageController carouselController) => this._pageController = carouselController;
/// Sets the [CircularBottomNavigationController] for the [CircularBottomNavigation]
set navigationController(CircularBottomNavigationController navController) => this._navigationController = navController;
/// Force the [CarouselSlider] to navigate to another page.
void gotoPage(int page) {
if (this._pageController != null)
this._pageController.jumpToPage(page);
set navigationController(CircularBottomNavigationController navController) {
this._navigationController = navController;
}
/// Forces the [CircularBottomNavigation] to the correct position.
void navbarToPage(int page) {
set navbar(int page) {
this._currentPage = page;
// Set to false to mitigate livelock.
this._triggerOriginNavbar = false;
// Guard
if (this._navigationController != null)
this._navigationController.value = (page + 1) % 3;
this._navigationController.value = (this._currentPage + 1) % 3;
this._previousPage = this._currentPage;
// Set to true once risk of livelock passed.
this._triggerOriginNavbar = true;
}
/// Forces the [PageController] to the correct position.
set page(int page) {
// Guard + Supplementary guard against livelock.
if (this._pageController != null && this._triggerOriginNavbar) {
// Navbar has pages in order [2, 0, 1] of the carousel.
this._currentPage = (page - 1) % 3;
// Make the carousel controller move forward (jump to +1 would limit UI because of lackluster cyclical handling).
if ((this._currentPage == 0 && this._previousPage == 2) || (this._currentPage == 1 && this._previousPage == 0) || (this._currentPage == 2 && this._previousPage == 1))
this._pageController.nextPage(duration: Duration(milliseconds: 200), curve: Curves.linear);
// Make the carousel controller move backward (jump to +1 would limit UI because of lackluster cyclical handling).
else if ((this._currentPage == 2 && this._previousPage == 0) || (this._currentPage == 0 && this._previousPage == 1) || (this._currentPage == 1 && this._previousPage == 2))
this._pageController.previousPage(duration: Duration(milliseconds: 200), curve: Curves.linear);
this._previousPage = this._currentPage;
}
}
}
\ No newline at end of file
......@@ -158,12 +158,9 @@ class MapDisplayController {
}
});
// TODO Set user defined duration
// TODO Set user defined radius
new Timer.periodic(Duration(minutes: 1), (Timer t) {
if (this._controller != null && this._controller.bounds != null) {
print('${this._controller.bounds.southWest}, ${this._controller.bounds.northEast}');
//print('${this._controller.bounds.southWest}, ${this._controller.bounds.northEast}');
NetworkHandler().getProximityPoints(DataHeader().deviceID, this._controller.bounds);
}
});
......
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
/// @ToDeprecate Describes the contents of the data packets
/// @ReplaceBy Includes the device name and a config_file URL
......@@ -23,6 +24,11 @@ class DataHeader {
this._headerSet = true;
}
void setHeaderFromExisting({String deviceId = '', String url = ''}) {
this._headerData = [deviceID, url];
this._headerSet = true;
}
/// Retrieve the deviceID from the header
String get deviceID => (this._headerData != null && this._headerData.length > 0) ? this._headerData[0] : '';
String get url => (this._headerData != null && this._headerData.length > 0) ? this._headerData[1] : '';
......@@ -46,4 +52,18 @@ class DataHeader {
this._headerSet = false;
this._headerData = [];
}
Stream<String> deviceIdStream() async* {
while(true) {
yield this.deviceID;
await Future.delayed(Duration(seconds: 1));
}
}
Stream<int> deviceBatteryLevel() async* {
while(true) {
yield -1;
await Future.delayed(Duration(seconds: 1));
}
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ import 'package:logair_application/utils/utils.dart';
class DataPacket {
List<int> _data;
int _timestamp;
String _deviceId;
/* Location variables */
double _latitude;
......@@ -33,6 +34,7 @@ class DataPacket {
String _extraData;
int timestamp() => _timestamp;
String deviceId() => _deviceId ?? '';
double latitude() => _latitude;
double longitude() => _longitude;
int altitude() => _altitude;
......@@ -80,6 +82,7 @@ class DataPacket {
// TODO Guard clause
DataPacket.fromMappedData(Map<String, dynamic> data) {
this._deviceId = data['device_id'];
this._timestamp = data['timestamp_nix'];
this._latitude = data['latitude'];
this._longitude = data['longitude'];
......
......@@ -4,17 +4,15 @@ import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:kotlin_flavor/scope_functions.dart';
import 'package:logair_application/logic/handlers/bluetooth_wake_handler.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
import 'package:logair_application/services/wake_service.dart';
import 'package:logair_application/utils/enums/bluetooth_connection_status.dart';
import 'package:logair_application/logic/handlers/data_handler.dart';
import 'package:logair_application/utils/enums/preference_keys.dart';
/// Serves as a wrapper class around [FlutterBlue], and helps integrate it into the application with extensions
class BTLEHandler {
/// Static values used by the chip.
/// TODO Optionize (ongoing see [PreferencesHandler]) ?
static final String bluetoothLeCc254xServiceUUID = "0000ffe0-0000-1000-8000-00805f9b34fb";
static final String bluetoothLeCc254xReadUUID = "0000ffe1-0000-1000-8000-00805f9b34fb";
/// [Singleton]() instantiation factory.
factory BTLEHandler() => _singleton;
......@@ -82,6 +80,7 @@ class BTLEHandler {
_device = null;
_bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING;
DataHandler().dispose();
BTWakeHandler().switchState(start: false);
return it.disconnect();
});
......@@ -94,6 +93,11 @@ class BTLEHandler {
bool success = false;
if (this._device != null) {
final String bluetoothLeCc254xServiceUUID = await PreferencesHandler().getPreferencesString(PreferenceKeys.BT__SERVICE_UUID.key);
final String bluetoothLeCc254xReadUUID = await PreferencesHandler().getPreferencesString(PreferenceKeys.BT__CHARACTERISTIC_UUID.key);
print('$bluetoothLeCc254xServiceUUID\n$bluetoothLeCc254xReadUUID');
/// Get the first round of [BluetoothService]s and apply filter to desired Service UUID.
services = await this._device.services.first;
services = services.where((s) => s.uuid.toString() == bluetoothLeCc254xServiceUUID).toList();
......@@ -162,5 +166,6 @@ class BTLEHandler {
await this._device.discoverServices();
await _setupCharacteristics();
await _setupListener();
BTWakeHandler().switchState(start: true);
}
}
\ No newline at end of file
......@@ -11,18 +11,12 @@ class BTWakeHandler {
static final BTWakeHandler _singleton = BTWakeHandler._internal();
/// TODO Used for debugging, remove
Timer _t;
/// TODO Used for debugging, remove
static Duration _duration = new Duration(seconds: 1);
static bool _wakeServiceActive = false;
bool switchState() {
if (_wakeServiceActive)
bool switchState({bool start}) {
if (_wakeServiceActive || !start)
_end();
else
else if (!_wakeServiceActive || start)
_start();
return _wakeServiceActive;
......@@ -31,12 +25,10 @@ class BTWakeHandler {
void _start() {
WakeService().modifyWakeService(true).then((value) => print('_device will stay awake $value'));
_wakeServiceActive = true;
_t = new Timer.periodic(_duration, (Timer t) => print("${new DateTime.now()}"));
}
void _end() {
WakeService().modifyWakeService(false).then((value) => print('_device will stay awake $value'));
_wakeServiceActive = false;
_t.cancel();
}
}
\ No newline at end of file
......@@ -4,13 +4,18 @@ import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:tuple/tuple.dart';
/// Handler that integrates a mysqli database into the application's workflow.
class DatabaseHandler {
factory DatabaseHandler() => _singleton;
Future<Database> _db;
DatabaseHandler._internal();
bool shouldCache;
static final DatabaseHandler _singleton = new DatabaseHandler._internal();
/// Reference to the internalized [Database] within this instance.
Future<Database> _db;
/// [Database] initialization string, to reduce repetition.
final String createString = '''CREATE TABLE data(
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT,
......@@ -32,6 +37,7 @@ class DatabaseHandler {
exported TINYINT DEFAULT 0
)''';
/// Retrieves the internal [Database] instance, and if not instantiated, instantiates it.
Future<Database> get _database async {
if (_db == null)
this._db = openDatabase(
......@@ -42,6 +48,7 @@ class DatabaseHandler {
return this._db;
}
/// Purges the [Database] of all internal.
Future<void> recreateDB() async {
final Database database = await _database;
......@@ -49,10 +56,7 @@ class DatabaseHandler {
await database.rawQuery(createString);
}
DatabaseHandler._internal();
static final DatabaseHandler _singleton = new DatabaseHandler._internal();
/// Inserts data into the [Database].
Future<void> insertData(DataPacket packet, String deviceId, String url) async {
final Database database = await _database;
Map<String, dynamic> insert = packet.toMap();
......@@ -65,6 +69,7 @@ class DatabaseHandler {
);
}
/// Retrieves all of the [DataPacket]s inserted into the [Database].
Future<List<DataPacket>> get data async {
final Database database = await _database;
......@@ -73,8 +78,10 @@ class DatabaseHandler {
return List.generate(data.length, (i) => DataPacket.fromMappedData(data[i]));
}
/// Closes the [Database] instance.
Future<void> close() async => (await this._database).close();
/// Streams the amount of elements contained within the [Database].
Stream<int> getDBSize() async* {
final Database database = await _database;
while (true) {
......@@ -84,6 +91,7 @@ class DatabaseHandler {
}
}
/// Retrieves all of the unsent packets in the [DataPacket].
Future<List<Tuple2<int, DataPacket>>> getUnsent(int limit) async {
final Database database = await _database;
final List<Map<String, dynamic>> data = await database.query('data',
......@@ -93,8 +101,6 @@ class DatabaseHandler {
orderBy: 'timestamp_nix ASC'
);
print(data);
return List.generate(data.length, (i) => Tuple2(data[i]['id'], DataPacket.fromMappedData(data[i])));
}
......@@ -121,17 +127,17 @@ class DatabaseHandler {
}
}
/// Retrieves the latest 5 seconds of data every 5 seconds.
Stream<Map<String, dynamic>> getLatest() async* {
final Database database = await _database;
while (true) {
// TODO Change query to timestamp - x time
final timestampLimit = DateTime.now().millisecondsSinceEpoch - 10000;
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT MIN(timestamp_nix) AS start, AVG(pm_1) AS pm_1, AVG(pm_2_5) AS pm_2_5, AVG(pm_4) AS pm_4, AVG(pm_10) AS pm_10 FROM (SELECT * FROM data WHERE timestamp_nix >= $timestampLimit ORDER BY id DESC LIMIT 5)');
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT MIN(timestamp_nix) AS start, AVG(pm_1) AS pm_1, AVG(pm_2_5) AS pm_2_5, AVG(pm_4) AS pm_4, AVG(pm_10) AS pm_10 FROM (SELECT * FROM data WHERE timestamp_nix >= ${DateTime.now().millisecondsSinceEpoch - 10000} ORDER BY id DESC LIMIT 5)');
yield data[0] ?? Map();
await Future.delayed(Duration(seconds: 5));
}
}
/// Retrieves the latest [maxResults] packets inserted into the [Database].
Future<List<DataPacket>> getLastPackets(int maxResults) async {
final Database database = await _database;
......
......@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:collection';
import 'package:connectivity/connectivity.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:http/http.dart' as http;
......@@ -9,6 +10,9 @@ 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/database_handler.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
import 'package:logair_application/utils/enums/preference_keys.dart';
import 'package:rxdart/rxdart.dart';
import 'package:tuple/tuple.dart';
/// This singleton data structure that contains services enabling the application to send data to a webserver.
......@@ -16,30 +20,81 @@ class NetworkHandler {
factory NetworkHandler() => _singleton;
NetworkHandler._internal() {
// TODO Preferences
this._pushFrequency.listen((value) {
if (value != this._oldPushFrequency) {
if (this._timer != null) {
this._timer.cancel();
}
this._timer = null;
this._duration = Duration(seconds: value);
this._timer = new Timer.periodic(this._duration, (Timer t) => this._sendDataToServer());
this._oldPushFrequency = value;
}
});
/// Define the interval between attempts to send the data to the server. (default: 1 minute).
const duration = const Duration(minutes: 1);
new Timer.periodic(duration, (Timer t) => this._sendDataToServer());
PreferencesHandler().getPreferencesInt(PreferenceKeys.NET__MAX_ITEMS_PER_PUSH.key).then((value) => this._pushAmount.value = value);
PreferencesHandler().getPreferencesInt(PreferenceKeys.NET__TIME_TO_PUSH.key).then((value) => this._pushFrequency.value = value);
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);
});
}
static final NetworkHandler _singleton = new NetworkHandler._internal();
@override
NetworkHandler.dispose() {
if (this._timer != null) {
this._timer.cancel();
this._timer = null;
}
_pushAmount.close();
_pushFrequency.close();
}
static final NetworkHandler _singleton = new NetworkHandler._internal();
DateTime _lastServerTransmission;
int a = 0;
BehaviorSubject<int> _pushAmount = BehaviorSubject<int>();
BehaviorSubject<int> _pushFrequency = BehaviorSubject<int>();
int _oldPushFrequency = -1;
Duration _duration;
Timer _timer;
Future<bool> checkNetworkAllowed() async {
ConnectivityResult connectivity = await Connectivity().checkConnectivity();
return !(connectivity == ConnectivityResult.none ||
(connectivity == ConnectivityResult.mobile && !(await PreferencesHandler().getPreferencesBool(PreferenceKeys.NET__USE_MOBILE_NET.key))));
}
/// Method to send acquired data packets by using a POST requests.
void _sendDataToServer() async {
// Check whether on mobile networks, and whether the user has allowed connectivity on mobile networks.
if (!(await checkNetworkAllowed() &&
await PreferencesHandler().getPreferencesBool(PreferenceKeys.GEN__SHARE.key))) {
return;
}
/// The default endpoint.
String url = 'https://api.logair.unige.ch/v1/service';
if (await DatabaseHandler().getUnsentPacketsLength().first > 0) {
DataPacket source = (await DatabaseHandler().getUnsent(_pushAmount.value ?? PreferenceKeys.NET__MAX_ITEMS_PER_PUSH.defaultValue))[0].item2;
// TODO set url / clean up
DataHeader().setHeaderFromExisting(deviceId: source.deviceId(), url: '');
}
/// Break conditions ([DataHeader] unset or no [DataPacket] available).
if (!DataHeader().headerSet || DataHandler().getData().length == 0)
else if (!DataHeader().headerSet || DataHandler().getData().length == 0)
return;
/// Get the map representation of a [DataHeader].
Map<String, dynamic> headerData = DataHeader().jsonify();
List<Tuple2<int, DataPacket>> databaseData = await DatabaseHandler().getUnsent(100);
List<Tuple2<int, DataPacket>> databaseData = await DatabaseHandler().getUnsent(_pushAmount.value ?? PreferenceKeys.NET__MAX_ITEMS_PER_PUSH.defaultValue);
/// Get all the [DataPacket]s to be sent (maximum 100).
List<DataPacket> packets = databaseData.map((x) => x.item2).toList();
......@@ -79,8 +134,12 @@ 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;
/// 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}';
/// Attempt to send the data to the endpoint via HTTP POST request.
......
......@@ -26,15 +26,17 @@ class PositionHandler {
/// The current [PermissionGroup] right's status.
GeolocationStatus _geolocationStatus;
int _timeSinceLastChange = 0;
/// Default location tweaking options.
/// TODO User configurable ?
LocationOptions locationOptions = LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 2);
/// TODO GPS.ACCURACY integrate
LocationOptions locationOptions = LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 0, timeInterval: 1);
/// Get whether the user has granted the location permissions.
Stream<GeolocationStatus> getGeolocationStatus() async* {
while (true) {
yield _geolocationStatus;
await Future.delayed(Duration(seconds: 2));
await Future.delayed(Duration(seconds: 1));
}
}
......@@ -53,7 +55,6 @@ class PositionHandler {
}
/// Try getting the current [Position], and if that fails get the last known [Position]
// TODO Deprecate Last Known Position after <n> seconds
Stream<Position> getCurrentOrLastPosition() async* {
while (true) {
Position currentPosition;
......@@ -61,17 +62,23 @@ class PositionHandler {
await this._getGeolocationPermission();
_permissionChecked = true;
}
if (_geolocationStatus == GeolocationStatus.granted)
currentPosition = await _geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
if (currentPosition != null)
_lastPosition = currentPosition;
if (currentPosition != null)
if (this._timeSinceLastChange >= 10 && _lastPosition != null && currentPosition == null)
this._lastPosition = null;
if (currentPosition != null) {
this._timeSinceLastChange = 0;
yield currentPosition;
else if (currentPosition == null && _lastPosition != null)
} else if (currentPosition == null && _lastPosition != null) {
this._timeSinceLastChange += 1;
yield _lastPosition;
else
} else
yield null;
await Future.delayed(Duration(milliseconds: 1000));
......
import 'package:logair_application/utils/enums/preference_keys.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tuple/tuple.dart';
/// Handler enabling the storage and use of preferences.
class PreferencesHandler {
Map<String, Tuple2<dynamic, dynamic>> _defaultValues = {
'BT.USING_ADVANCED': Tuple2(bool, false),
'BT.FREQUENT': Tuple2(String, '[ ]'),
'BT.SERVICE_UUID': Tuple2(String, '0000ffe0-0000-1000-8000-00805f9b34fb'),
'BT.CHARACTERISTIC_UUID': Tuple2(String, '0000ffe1-0000-1000-8000-00805f9b34fb'),
'MAP.USING_ADVANCED': Tuple2(bool, false),
'MAP.PROX.MAX_PER_QUERY': Tuple2(int, 200),
'MAP.PROX.MAX_AGE_SECONDS': Tuple2(int, 200)
};
static final PreferencesHandler _singleton = new PreferencesHandler._internal();
factory PreferencesHandler() => _singleton;
PreferencesHandler._internal() {
this._sharedPreferences;
}
/// The singleton reference to the [SharedPreferences] instance.
SharedPreferences _sharedPreferencesInternal;
/// Whether the [SharedPreferences] have been initialized with default values.
bool _setup = false;
/// This enables the retrieval of the [SharedPreferences] ecosystem and setting it up if needed first.
Future<SharedPreferences> get _sharedPreferences async {
if (_sharedPreferencesInternal == null) {
if (_sharedPreferencesInternal == null && !this._setup) {
/// If some of the [PreferenceKeys] have mismatched types set to their default values.
if (!PreferenceKeys.assertTypesMatch())
throw new Exception('A <defaultValue<T>, T> pair is mismatched');
this._sharedPreferencesInternal = await SharedPreferences.getInstance();
/// Initial Setup
_defaultValues.forEach((key, value) {
PreferenceKeys.list.forEach((PreferenceKeys prefs) {
Function setter;
switch (value.item1) {
switch (prefs.type) {
case int:
setter = this._sharedPreferencesInternal.setInt;
break;
......@@ -36,58 +43,78 @@ class PreferencesHandler {
default:
throw ArgumentError('Unexpected property type');
}
if (!this._sharedPreferencesInternal.containsKey(key)) {
setter(key, value.item2);
setter(key + '_DEFAULT', value.item2);
if (!this._sharedPreferencesInternal.containsKey(prefs.key)) {
setter(prefs.key, prefs.defaultValue);
setter(prefs.defaultKey, prefs.defaultValue);
}
});
print('Setup preferences \n ${List.generate(this._sharedPreferencesInternal.getKeys().length, (i) => this._sharedPreferencesInternal.getKeys().elementAt(i) + ' => ' + this._sharedPreferencesInternal.get(this._sharedPreferencesInternal.getKeys().elementAt(i)).toString()).join("\n")}');
});
//print('Setup preferences done\n ${List.generate(this._sharedPreferencesInternal.getKeys().length, (i) => this._sharedPreferencesInternal.getKeys().elementAt(i) + ' => ' + this._sharedPreferencesInternal.get(this._sharedPreferencesInternal.getKeys().elementAt(i)).toString()).join("\n")}');
this._setup = true;
}
return this._sharedPreferencesInternal;
}
PreferencesHandler._internal();
/// Retrieves an [int] from the [SharedPreferences].
Future<int> getPreferencesInt(String key) async => (await this._sharedPreferences).getInt(key);