...
 
Commits (2)
  • Nicolas Richard Walter Boeckh's avatar
    Redundant · c378db21
    Nicolas Richard Walter Boeckh authored
    c378db21
  • Nicolas Richard Walter Boeckh's avatar
    DB, Settings+, Graph · 04f3929c
    Nicolas Richard Walter Boeckh authored
    DB:
    
    - DataHandler pumps to DB
    - Network takes from DB and sets export flag
    - DataPacket has overloaded constructors depending on reconstruction usage.
    
    Settings :
      - Layout (ExpansionTile)
      - WIP Fix RenderFlex Error
      - MapSettings Removed, added to Settings.
    
    Graph:
    - Graph WIP (takes from DB).
    - TODO State Management.
    
    Bugs:
    - BTLE reco/deco
    - RenderFlex in Settings
    - Values not updating in graph, state management.
    
    - Found
    04f3929c
{"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-07-10 18:41:09.189089","version":"1.20.0-3.0.pre.126"}
\ No newline at end of file
{"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-07-13 03:41:01.914636","version":"1.19.0-2.0.pre.49"}
\ No newline at end of file
......@@ -29,8 +29,20 @@ class DataHeader {
/// @Getter for _headerSet;
bool get headerSet => this._headerSet;
Stream<bool> getWhenHeaderSet() async* {
while (true) {
yield this._headerSet;
await Future.delayed(Duration(seconds: 1));
}
}
bool isEqual(List<int> that) => ListEquality().equals(_headerData, that);
// TODO
Map<String, dynamic> jsonify() => { 'device_id' : this._headerData[0], 'url': '000000000000' };
void dispose() {
this._headerSet = false;
this._headerData = [];
}
}
\ No newline at end of file
......@@ -15,7 +15,7 @@ class DataPacket {
double _latitude;
double _longitude;
int _altitude;
double _heading;
int _heading;
double _speed;
/* Ambient metrics */
......@@ -36,7 +36,7 @@ class DataPacket {
double latitude() => _latitude;
double longitude() => _longitude;
int altitude() => _altitude;
double heading() => _heading;
int heading() => _heading;
double speed() => _speed;
double temperature() => _temperature;
double pressure() => _pressure;
......@@ -52,7 +52,7 @@ class DataPacket {
this._latitude = (acquiredData[0] != '') ? parseDoubleWrapper(acquiredData[0]) : null;
this._longitude = (acquiredData[1] != '') ? parseDoubleWrapper(acquiredData[1]) : null;
this._altitude = (acquiredData[2] != '') ? int.tryParse(acquiredData[2]) : null;
this._heading = (acquiredData[3] != '') ? parseDoubleWrapper(acquiredData[3]) : null;
this._heading = (acquiredData[3] != '') ? int.tryParse(acquiredData[3]) : null;
this._speed = (acquiredData[4] != '') ? parseDoubleWrapper(acquiredData[4]) : null;
this._temperature = (acquiredData[5] != '') ? parseDoubleWrapper(acquiredData[5]) : null;
......@@ -70,13 +70,31 @@ class DataPacket {
this._latitude = position.latitude;
this._longitude = position.longitude;
this._altitude = position.altitude.toInt();
this._heading = position.heading;
this._heading = position.heading.round() % 360;
this._speed = position.speed;
}
}
}
DataPacket.fromData(this._timestamp, this._latitude, this._longitude, this._altitude, this._heading, this._speed, this._temperature, this._pressure, this._relativeHumidity, this._pm1, this._pm2_5, this._pm4, this._pm10, this._extraData);
DataPacket.fromData(this._timestamp, this._latitude, this._longitude, this._altitude, this._speed, this._heading, this._temperature, this._pressure, this._relativeHumidity, this._pm1, this._pm2_5, this._pm4, this._pm10, this._extraData);
// TODO Guard clause
DataPacket.fromMappedData(Map<String, dynamic> data) {
this._timestamp = data['timestamp_nix'];
this._latitude = data['latitude'];
this._longitude = data['longitude'];
this._altitude = data['altitude'];
this._speed = data['speed'];
this._heading = data['heading'];
this._temperature = data['temperature'];
this._relativeHumidity = data['relative_humidity'];
this._pressure = data['pressure'];
this._pm1 = data['pm_1'];
this._pm2_5 = data['pm_2_5'];
this._pm4 = data['pm_4'];
this._pm10 = data['pm_10'];
this._extraData = data['extra'];
}
String stringify() => '${this._latitude}, ${this._longitude}, ${this._temperature}, ${this._pressure}, ${this._relativeHumidity}, ${this._pm2_5}, ${this._pm10}';
......@@ -117,6 +135,6 @@ class DataPacket {
@override
String toString() {
return 'DataPacket{time: $_timestamp, lat: $_latitude, long: $_longitude, pm1: $_pm1, pm2.5: $_pm2_5, pm4: $_pm4, pm10: $_pm10';
return 'DataPacket{time: $_timestamp, lat: $_latitude, long: $_longitude, pm1: $_pm1, pm2.5: $_pm2_5, pm4: $_pm4, pm10: $_pm10}';
}
}
\ No newline at end of file
......@@ -80,7 +80,7 @@ class BTLEHandler {
_characteristic = null;
_device = null;
_bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING;
DataHandler().dispose();
return it.disconnect();
});
......
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/logic/data_header.dart';
import 'package:logair_application/logic/data_packet.dart';
import 'package:logair_application/logic/handlers/database_handler.dart';
class DataHandler {
factory DataHandler() => _singleton;
......@@ -8,7 +9,7 @@ class DataHandler {
DataHandler._internal();
static const HEADER_SIZE = 11;
static final DataHandler _singleton = new DataHandler._internal();
static final DataHandler _singleton = new DataHandler._internal();
/// [List] containing all of the data received by this.
List<int> _data = [];
......@@ -49,11 +50,18 @@ class DataHandler {
/// If it's a [DataHeader] instance.
if (packet[0] == 123) {
print('HEADER ${String.fromCharCodes(packet)}');
bool wasSet = DataHeader().headerSet;
DataHeader().setHeader(packet);
/// Retroactively add all previous packets
if (!wasSet && DataHeader().headerSet)
_sortedData.forEach((packet) => DatabaseHandler().insertData(packet, DataHeader().deviceID));
/// Or if it is a [DataPacket] instance.
} else if (packet[0] == 91) {
_sortedData.add(DataPacket(packet));
print('PACKET ${String.fromCharCodes(packet)}');
if (DataHeader().headerSet)
DatabaseHandler().insertData(_sortedData.last, DataHeader().deviceID);
}
}
/// TODO Used for debugging, later
......@@ -89,11 +97,13 @@ class DataHandler {
}
}
@deprecated
/// Used to monitor the amount of unsent [DataPacket]s.
Stream<int> getUnsentPacketsLength() async* {
while (true) {
yield (_sortedData.length);
await Future.delayed(Duration(seconds: 1));
}
}
void dispose() {
DataHeader().dispose();
this._data.removeWhere((element) => true);
}
}
\ No newline at end of file
import 'package:logair_application/logic/data_header.dart';
import 'package:logair_application/logic/data_packet.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:tuple/tuple.dart';
class DatabaseHandler {
factory DatabaseHandler() => _singleton;
Future<Database> _db;
bool shouldCache;
Future<Database> get _database async {
if (_db == null)
this._db = openDatabase(
......@@ -29,21 +33,50 @@ class DatabaseHandler {
pm_2_5 FLOAT,
pm_4 FLOAT,
pm_10 FLOAT,
extra TEXT
extra TEXT,
exported TINYINT DEFAULT 0
)''')
);
return this._db;
}
// TODO REMOVE !
Future<void> recreateDB() async {
final Database database = await _database;
await database.rawQuery('DROP TABLE IF EXISTS data');
await database.rawQuery('''CREATE TABLE data(
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT,
timestamp_nix INTEGER,
latitude REAL,
longitude REAL,
altitude INTEGER,
speed REAL,
heading INTEGER,
temperature FLOAT,
relative_humidity FLOAT,
pressure FLOAT,
pm_1 FLOAT,
pm_2_5 FLOAT,
pm_4 FLOAT,
pm_10 FLOAT,
extra TEXT,
exported TINYINT DEFAULT 0
)''');
}
DatabaseHandler._internal();
static final DatabaseHandler _singleton = new DatabaseHandler._internal();
Future<void> insertData(DataPacket packet) async {
Future<void> insertData(DataPacket packet, String deviceId) async {
final Database database = await _database;
Map<String, dynamic> insert = packet.toMap();
insert['device_id'] = deviceId;
await database.insert(
'data',
packet.toMap(),
insert,
conflictAlgorithm: ConflictAlgorithm.replace
);
}
......@@ -53,26 +86,59 @@ class DatabaseHandler {
final List<Map<String, dynamic>> data = await database.query('data');
return List.generate(data.length, (i) => DataPacket.fromData(
data[i]['timestamp_nix'],
data[i]['latitude'],
data[i]['longitude'],
data[i]['altitude'],
data[i]['speed'],
data[i]['heading'],
data[i]['temperature'],
data[i]['relative_humidity'],
data[i]['pressure'],
data[i]['pm_1'],
data[i]['pm_2_5'],
data[i]['pm_4'],
data[i]['pm_10'],
data[i]['extra'],
)
);
return List.generate(data.length, (i) => DataPacket.fromMappedData(data[i]));
}
Future<int> deleteAll() async => await (await this._database).delete('data');
Future<void> close() async => (await this._database).close();
Stream<int> getDBSize() async* {
final Database database = await _database;
while (true) {
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT COUNT(*) AS count FROM data');
yield data[0]['count'] ?? 0;
await Future.delayed(Duration(seconds: 5));
}
}
Future<List<Tuple2<int, DataPacket>>> getUnsent(int limit) async {
final Database database = await _database;
final List<Map<String, dynamic>> data = await database.query('data',
where: 'exported = ?',
whereArgs: [0],
limit: limit,
orderBy: 'timestamp_nix ASC'
);
print(data);
return List.generate(data.length, (i) => Tuple2(data[i]['id'], DataPacket.fromMappedData(data[i])));
}
Future<void> setExported(List<int> ids) async {
final Database database = await _database;
print('UPDATE data SET exported = 1 WHERE id IN (${ids.join(', ')})');
int count = await database.rawUpdate('UPDATE data SET exported = 1 WHERE id IN (${ids.join(', ')})');
print('Exported $count');
}
/// Used to monitor the amount of unsent [DataPacket]s.
Stream<int> getUnsentPacketsLength() async* {
final Database database = await _database;
while (true) {
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT COUNT(*) AS count FROM data WHERE exported = 0');
yield data[0]['count'] ?? 0;
await Future.delayed(Duration(seconds: 1));
}
}
Stream<Map<String, dynamic>> getLatest() async* {
final Database database = await _database;
while (true) {
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 ORDER BY id DESC LIMIT 5)');
yield data[0] ?? Map();
await Future.delayed(Duration(seconds: 5));
}
}
}
\ No newline at end of file
......@@ -8,6 +8,8 @@ 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/database_handler.dart';
import 'package:tuple/tuple.dart';
/// Singleton data structure that contains services enabling the application to send data to a webserver.
/// TODO Make URL user configurable.
......@@ -15,6 +17,7 @@ class NetworkHandler {
factory NetworkHandler() => _singleton;
NetworkHandler._internal() {
// TODO Preferences
/// 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());
......@@ -26,6 +29,7 @@ class NetworkHandler {
/// Method to send acquired data packets by using a POST requests.
void _sendDataToServer() async {
// TODO Preferences
/// The default endpoint.
String url = 'https://api.logair.unige.ch/v1/service';
......@@ -36,8 +40,10 @@ class NetworkHandler {
/// Get the map representation of a [DataHeader].
Map<String, dynamic> headerData = DataHeader().jsonify();
List<Tuple2<int, DataPacket>> databaseData = await DatabaseHandler().getUnsent(100);
/// Get all the [DataPacket]s to be sent (maximum 100).
List<DataPacket> packets = DataHandler().getDataRange(100);
List<DataPacket> packets = databaseData.map((x) => x.item2).toList();
/// Convert the [DataPacket] list to a [Map] of their [List] representations.
Map<String, List<List<dynamic>>> packetList = {
......@@ -62,7 +68,8 @@ class NetworkHandler {
if (response != null && response.statusCode == 200) {
for (int i = 0; i < packets.length; i++) {
DataHandler().pop();
}
}
await DatabaseHandler().setExported(databaseData.map((x) => x.item1).toList());
print('RESPONSE ${response.statusCode}');
this._lastServerTransmission = DateTime.now();
}
......
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();
SharedPreferences _sharedPreferences;
SharedPreferences _sharedPreferencesInternal;
PreferencesHandler._internal() {
SharedPreferences.getInstance().then((SharedPreferences prefs) {
this._sharedPreferences = prefs;
Future<SharedPreferences> get _sharedPreferences async {
if (_sharedPreferencesInternal == null) {
this._sharedPreferencesInternal = await SharedPreferences.getInstance();
/// Initial Setup
if (!prefs.containsKey('BT.USING_ADVANCED')) prefs.setBool('BT.USING_ADVANCED', false);
if (!prefs.containsKey('BT.USING_ADVANCED_DEFAULT')) prefs.setBool('BT.USING_ADVANCED_DEFAULT', false);
if (!prefs.containsKey('BT.FREQUENT')) prefs.setString('BT.FREQUENT', '[ ]');
if (!prefs.containsKey('BT.FREQUENT_DEFAULT')) prefs.setString('BT.FREQUENT_DEFAULT', '[ ]');
_defaultValues.forEach((key, value) {
Function setter;
switch (value.item1) {
case int:
setter = this._sharedPreferencesInternal.setInt;
break;
case bool:
setter = this._sharedPreferencesInternal.setBool;
break;
case String:
setter = this._sharedPreferencesInternal.setString;
break;
default:
throw ArgumentError('Unexpected property type');
}
if (!this._sharedPreferencesInternal.containsKey(key)) {
setter(key, value.item2);
setter(key + '_DEFAULT', value.item2);
}
});
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")}');
}
if (!prefs.containsKey('BT.SERVICE_UUID')) prefs.setString('BT.SERVICE_UUID', '0000ffe0-0000-1000-8000-00805f9b34fb');
if (!prefs.containsKey('BT.SERVICE_UUID_DEFAULT')) prefs.setString('BT.SERVICE_UUID_DEFAULT', '0000ffe0-0000-1000-8000-00805f9b34fb');
return this._sharedPreferencesInternal;
}
if (!prefs.containsKey('BT.CHARACTERISTIC_UUID')) prefs.setString('BT.CHARACTERISTIC_UUID', '0000ffe1-0000-1000-8000-00805f9b34fb');
if (!prefs.containsKey('BT.CHARACTERISTIC_UUID_DEFAULT')) prefs.setString('BT.CHARACTERISTIC_UUID_DEFAULT', '0000ffe1-0000-1000-8000-00805f9b34fb');
PreferencesHandler._internal();
if (!prefs.containsKey('MAP.USING_ADVANCED')) prefs.setBool('MAP.USING_ADVANCED', false);
if (!prefs.containsKey('MAP.USING_ADVANCED_DEFAULT')) prefs.setBool('MAP.USING_ADVANCED_DEFAULT', false);
Future<int> getPreferencesInt(String key) async => (await this._sharedPreferences).getInt(key);
Future<String> getPreferencesString(String key) async => (await this._sharedPreferences).getString(key);
Future<bool> getPreferencesBool(String key) async => (await this._sharedPreferences).getBool(key);
if (!prefs.containsKey('MAP.PROX.MAX_PER_QUERY')) prefs.setInt('MAP.PROX.MAX_PER_QUERY', 200);
if (!prefs.containsKey('MAP.PROX.MAX_PER_QUERY_DEFAULT')) prefs.setInt('MAP.PROX.MAX_PER_QUERY_DEFAULT', 200);
Future<dynamic> getPreferences(String key) async => (await this._sharedPreferences).get(key);
if (!prefs.containsKey('MAP.PROX.MAX_AGE_SECONDS')) prefs.setInt('MAP.PROX.MAX_AGE_SECONDS', 200);
if (!prefs.containsKey('MAP.PROX.MAX_AGE_SECONDS_DEFAULT')) prefs.setInt('MAP.PROX.MAX_AGE_SECONDS_DEFAULT', 200);
Future<void> setPreferencesInt(String key, int value) async => (await this._sharedPreferences).setInt(key, value);
Future<void> setPreferencesString(String key, String value) async => (await this._sharedPreferences).setString(key, value);
Future<void> setPreferencesBool(String key, bool value) async => (await this._sharedPreferences).setBool(key, value);
Future<void> resetPreferences<T>(String key) async {
Function getter, setter;
switch (T) {
case int:
getter = getPreferencesInt;
setter = setPreferencesInt;
break;
case bool:
getter = getPreferencesBool;
setter = setPreferencesBool;
break;
case String:
getter = getPreferencesString;
setter = setPreferencesString;
}
print('Setup Prefs ${prefs.getKeys()}');
});
T value = await getter(key + '_DEFAULT');
await setter(key, value);
}
int getPreferencesInt(String key) => this._sharedPreferences.getInt(key);
String getPreferencesString(String key) => this._sharedPreferences.getString(key);
bool getPreferencesBool(String key) => this._sharedPreferences.getBool(key);
void setPreferencesInt(String key, int value) => this._sharedPreferences.setInt(key, value);
void setPreferencesString(String key, String value) => this._sharedPreferences.setString(key, value);
void setPreferencesBool(String key, bool value) => this._sharedPreferences.setBool(key, value);
void resetAllPreferences() async {
await (await this._sharedPreferences).setBool('BT.USING_ADVANCED', false);
await (await this._sharedPreferences).setString('BT.FREQUENT', '[ ]');
await (await this._sharedPreferences).setString('BT.SERVICE_UUID', '0000ffe0-0000-1000-8000-00805f9b34fb');
await (await this._sharedPreferences).setString('BT.CHARACTERISTIC_UUID', '0000ffe1-0000-1000-8000-00805f9b34fb');
await (await this._sharedPreferences).setBool('MAP.USING_ADVANCED', false);
await (await this._sharedPreferences).setInt('MAP.PROX.MAX_PER_QUERY', 200);
await (await this._sharedPreferences).setInt('MAP.PROX.MAX_AGE_SECONDS', 200);
}
factory PreferencesHandler() => _singleton;
}
\ No newline at end of file
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
/// This [Widget] is a [Form] that enables users to change settings.
/// It interacts with the [PreferencesHandler] to affect changes that will be maintained on app restart.
class SettingsForm extends StatefulWidget {
SettingsForm({Key key});
@override
State<StatefulWidget> createState() => _SettingsFormState(key: key);
}
class _SettingsFormState extends State<SettingsForm> {
_SettingsFormState({Key key}) {
PreferencesHandler().getPreferencesBool('BT.USING_ADVANCED').then((value) {
if (this._advancedBTOptions == null)
setState(() {
this._advancedBTOptions = value;
});
});
PreferencesHandler().getPreferencesBool('MAP.USING_ADVANCED').then((value) {
if (this._advancedMapOptions == null)
setState(() {
this._advancedMapOptions = value;
});
});
}
final _formKey = GlobalKey<FormState>(debugLabel: 'FORM_GENERAL');
bool _advancedBTOptions;
bool _advancedMapOptions;
final _bleServiceController = TextEditingController();
final _bleCharacteristicController = TextEditingController();
final _mapQueryLimitController = TextEditingController();
final _mapQueryAgeController = TextEditingController();
@override
void dispose() {
// Clean up the controller when the widget is disposed.
_bleServiceController.dispose();
_bleCharacteristicController.dispose();
_mapQueryLimitController.dispose();
_mapQueryAgeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
/// The [Form] is comprised of multiple [ExpansionTile]s that represent settings subsections from general app management to more advanced settings.
return Form(
key: _formKey,
child: Column(
children: [
// TODO Fix renderFlex overflow
SingleChildScrollView(
child: Column(
children: [
// NETWORKING
ExpansionTile(
title: Text('Network'),
initiallyExpanded: true,
leading: Icon(Icons.network_check),
children: [
ListTile(title: Text('TODO: Max number of items per push (def 100)')),
ListTile(title: Text('TODO: Push Frequency')),
ListTile(title: Text('TODO: Push Destination')),
ListTile(title: Text('TODO: Push on 3G/4G')),
],
),
Divider(color: Colors.black,),
// CACHING
ExpansionTile(
title: Text('Local Data'),
initiallyExpanded: true,
leading: Icon(Icons.storage),
children: [
ListTile(title: Text('TODO: Delete local data'))
],
),
Divider(color: Colors.black,),
// BLUETOOTH
ExpansionTile(
title: Text('Bluetooth'),
subtitle: Text('(Advanced)'),
leading: Icon(Icons.bluetooth),
children: [
CheckboxListTile(
title: Text('Modify advanced bluetooth settings'),
value: _advancedBTOptions ?? false,
onChanged: (newValue) => setState(() {
_advancedBTOptions = newValue;
}),
controlAffinity: ListTileControlAffinity.leading,
),
Visibility(
visible: _advancedBTOptions ?? false,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text(
'Changing these settings can break device data acquisition',
style: TextStyle(
color: Colors.red[600],
fontSize: 12
),
textAlign: TextAlign.center,
),
),
),
// TODO Service / Characteristic Explorer ?
_buildInputTile(_bleServiceController, 'BLE Service UUID', 'BT.SERVICE_UUID', _advancedBTOptions, TextInputType.text, <TextInputFormatter>[ ], RegExp('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')),
_buildInputTile(_bleCharacteristicController, 'BLE Characteristic UUID', 'BT.CHARACTERISTIC_UUID', _advancedBTOptions, TextInputType.text, <TextInputFormatter>[ ], RegExp('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')),
],
),
Divider(color: Colors.black,),
// MAP
ExpansionTile(
title: Text('Map'),
subtitle: Text('(Advanced)'),
leading: Icon(Icons.map),
children: [
CheckboxListTile(
title: Text('Modify advanced map settings'),
value: _advancedMapOptions ?? false,
onChanged: (newValue) {
Future.delayed(Duration.zero, () => {
if (newValue != _advancedMapOptions) setState(() {
_advancedMapOptions = newValue;
})
});
},
controlAffinity: ListTileControlAffinity.leading,
),
Visibility(
visible: _advancedMapOptions ?? false,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text(
'Changing these settings can have serious performance impacts on your device and battery life',
style: TextStyle(
color: Colors.red[600],
fontSize: 12
),
textAlign: TextAlign.center,
),
),
),
_buildInputTile(_mapQueryLimitController, 'Maximum other points', 'MAP.PROX.MAX_PER_QUERY', _advancedMapOptions, TextInputType.number, <TextInputFormatter>[ WhitelistingTextInputFormatter.digitsOnly ], RegExp(r'\d+')),
_buildInputTile(_mapQueryAgeController, 'Maximum age of other points (seconds)', 'MAP.PROX.MAX_AGE_SECONDS', _advancedMapOptions, TextInputType.number, <TextInputFormatter>[ WhitelistingTextInputFormatter.digitsOnly ], RegExp(r'\d+')),
],
),
],
),
),
Spacer(flex: 1), // To force the row at the bottom of the screen
/// Action buttons (Confirm, Reset)
Row(
children: [
Expanded(
flex: 1,
child: RaisedButton(
onPressed: () {
print(_formKey.currentState.validate());
if (_formKey.currentState.validate()) {
PreferencesHandler().setPreferencesBool('BT.USING_ADVANCED', _advancedBTOptions);
PreferencesHandler().setPreferencesString('BT.SERVICE_UUID', _bleServiceController.text);
PreferencesHandler().setPreferencesString('BT.CHARACTERISTIC_UUID', _bleCharacteristicController.text);
PreferencesHandler().setPreferencesBool('MAP.USING_ADVANCED', _advancedMapOptions);
}
},
child: Text('Confirm Changes'),
),
),
Expanded(
flex: 1,
child: RaisedButton(
onPressed: () async {
PreferencesHandler().resetAllPreferences();
_formKey.currentState.reset();
},
child: Text('Reset values'),
),
),
],
)
],
)
);
}
/// This method builds a tile that enables a user to change the value of a setting.
/// TODO Individual field reset button key + '_DEFAULT'.
Widget _buildInputTile(TextEditingController controller, String labelText, String key, bool enabled, TextInputType inputType, List<TextInputFormatter> inputFormatters, RegExp validation) {
PreferencesHandler().getPreferences(key).then((value) => controller.text = value.toString());
return Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: TextFormField(
controller: controller,
enabled: enabled,
decoration: InputDecoration(
labelText: labelText,
contentPadding: EdgeInsets.zero,
),
keyboardType: inputType,
inputFormatters: inputFormatters,
validator: (value) {
if (value.isEmpty) {
return 'The field cannot be empty';
}
RegExpMatch match = validation.firstMatch(value);
if (match != null && match.input != value) {
return 'This is an invalid value';
}
return null;
}
)
);
}
}
\ No newline at end of file
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/logic/handlers/database_handler.dart';
import 'package:logair_application/ui/components/graph/graph_colors.dart';
import 'package:logair_application/ui/components/graph/indicator.dart';
/// WIP
/// TODO Fix display (statefulness ? Yes)
/// TODO Clear FL Spots
/// This graph should display the average quality of the air in 5 second increments.
class Graph extends StatefulWidget {
Graph({Key key}) : super(key: key);
......@@ -12,84 +17,71 @@ class Graph extends StatefulWidget {
}
class _GraphState extends State<Graph> {
_GraphState({Key key});
_GraphState({Key key}) {
DatabaseHandler().getLatest().listen((newData) {
this.start = this.start ?? newData['start'].toDouble();
this.now = (newData['start'].toDouble() - this.start) / 1000;
print(newData);
if (newData['pm_1'] != null)
spotsPM1.add(FlSpot(this.now, newData['pm_1']));
if (newData['pm_2_5'] != null)
spotsPM2_5.add(FlSpot(this.now, newData['pm_2_5']));
if (newData['pm_4'] != null)
spotsPM4.add(FlSpot(this.now, newData['pm_4']));
if (newData['pm_10'] != null)
spotsPM10.add(FlSpot(this.now, newData['pm_10']));
});
}
int touchedIndex;
double start;
double now;
List<LineChartBarData> linesBarData1() {
final LineChartBarData lineChartBarData1 = LineChartBarData(
spots: [
FlSpot(1, 1),
FlSpot(3, 1.5),
FlSpot(5, 1.4),
FlSpot(7, 3.4),
FlSpot(10, 2),
FlSpot(12, 2.2),
FlSpot(13, 1.8),
],
isCurved: false,
colors: [
GraphColors.PM1.color,
],
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
);
static List<FlSpot> spotsPM1 = [FlSpot(0, 0)];
static List<FlSpot> spotsPM2_5 = [FlSpot(0, 0)];
static List<FlSpot> spotsPM4 = [FlSpot(0, 0)];
static List<FlSpot> spotsPM10 = [FlSpot(0, 0)];
final LineChartBarData lineChartBarData2 = LineChartBarData(
spots: [
FlSpot(1, 1),
FlSpot(3, 2.8),
FlSpot(7, 1.2),
FlSpot(10, 2.8),
FlSpot(12, 2.6),
FlSpot(13, 3.9),
],
colors: [
GraphColors.PM2_5.color,
],
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
);
final LineChartBarData lineChartBarData3 = LineChartBarData(
spots: [
FlSpot(1, 2.8),
FlSpot(3, 1.9),
FlSpot(6, 3),
FlSpot(10, 1.3),
FlSpot(13, 2.5),
],
colors: [
GraphColors.PM4.color,
],
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
final LineChartBarData linePM1 = LineChartBarData(
spots: spotsPM1,
isCurved: false,
colors: [
GraphColors.PM1.color,
],
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
);
);
final LineChartBarData lineChartBarData4 = LineChartBarData(
spots: [
FlSpot(1, 1.8),
FlSpot(3, 4.9),
FlSpot(6, 4),
FlSpot(10, 12),
FlSpot(13, 12.5),
],
colors: [
GraphColors.PM10.color,
],
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
);
return [
lineChartBarData1,
lineChartBarData2,
lineChartBarData3,
lineChartBarData4,
];
}
final LineChartBarData linePM2_5 = LineChartBarData(
spots: spotsPM2_5,
colors: [
GraphColors.PM2_5.color,
],
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
);
final LineChartBarData linePM4 = LineChartBarData(
spots: spotsPM4,
colors: [
GraphColors.PM4.color,
],
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
);
final LineChartBarData linePM10 = LineChartBarData(
spots: spotsPM10,
colors: [
GraphColors.PM10.color,
],
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
);
@override
Widget build(BuildContext context) {
......@@ -124,7 +116,7 @@ class _GraphState extends State<Graph> {
margin: 10,
getTitles: (value) {
if (value % 10 == 0)
return 'v';
return value.toString();
return '';
},
),
......@@ -136,10 +128,8 @@ class _GraphState extends State<Graph> {
fontSize: 12,
),
getTitles: (value) {
switch (value.toInt()) {
case 1:
return 'WIP';
}
if (value % 5 == 0)
return value.toString();
return '';
},
margin: 8,
......@@ -154,9 +144,17 @@ class _GraphState extends State<Graph> {
),
),
),
minX: 0,
minX: (now ?? 0) - 10,
maxX: (now ?? 0),
maxY: 10,
minY: 0,
lineBarsData: linesBarData1(),
clipToBorder: true,
lineBarsData: [
linePM1,
linePM2_5,
linePM4,
linePM10
],
)
),
),
......@@ -207,5 +205,4 @@ class _GraphState extends State<Graph> {
]
);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:logair_application/ui/components/graph/graph.dart';
/// Enumeration of representative colors for the different [Graph] lines.
class GraphColors {
final String _key;
final Color _value;
......
import 'package:flutter/material.dart';
import 'package:logair_application/ui/components/graph/graph.dart';
/// Small label [Widget] that serves as Caption for the [Graph]
class Indicator extends StatelessWidget {
final Color color;
final String text;
......@@ -18,24 +20,27 @@ class Indicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: isSquare ? BoxShape.rectangle : BoxShape.circle,
color: color,
return Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: Row(
children: <Widget>[
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: isSquare ? BoxShape.rectangle : BoxShape.circle,
color: color,
),
),
),
const SizedBox(
width: 4,
),
Text(
text,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: textColor),
)
],
const SizedBox(
width: 4,
),
Text(
text,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: textColor),
)
],
)
);
}
}
\ No newline at end of file
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
class MapSettingsForm extends StatefulWidget {
MapSettingsForm({Key key});
@override
State<StatefulWidget> createState() => _MapSettingsFormState(key: key);
}
class _MapSettingsFormState extends State<MapSettingsForm> {
_MapSettingsFormState({Key key});
final _formKey = GlobalKey<FormState>();
bool _advancedBTOptions = PreferencesHandler().getPreferencesBool('BT.USING_ADVANCED');
bool _advancedMapOptions = PreferencesHandler().getPreferencesBool('MAP.USING_ADVANCED');
final _bleServiceController = TextEditingController();
final _bleCharacteristicController = TextEditingController();
final _mapQueryLimitController = TextEditingController();
final _mapQueryAgeController = TextEditingController();
@override
void dispose() {
// Clean up the controller when the widget is disposed.
_bleServiceController.dispose();
_bleCharacteristicController.dispose();
_mapQueryLimitController.dispose();
_mapQueryAgeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
CheckboxListTile(
title: Text('Modify advanced bluetooth settings'),
value: _advancedBTOptions,
onChanged: (newValue) => setState(() {
_advancedBTOptions = newValue;
_advancedMapOptions = _advancedMapOptions;
}),
controlAffinity: ListTileControlAffinity.leading,
),
Visibility(
visible: _advancedBTOptions,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text(
'Changing these settings can break device data acquisition',
style: TextStyle(
color: Colors.red[600],
fontSize: 12
),
textAlign: TextAlign.center,
),
),
),
// TODO Service / Characteristic Explorer ?
_buildInputTile('BLE Service UUID', PreferencesHandler().getPreferencesString('BT.SERVICE_UUID'), _advancedBTOptions, TextInputType.text, <TextInputFormatter>[ ], RegExp('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')),
_buildInputTile('BLE Characteristic UUID', PreferencesHandler().getPreferencesString('BT.CHARACTERISTIC_UUID'), _advancedBTOptions, TextInputType.text, <TextInputFormatter>[ ], RegExp('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')),
Divider(color: Colors.black,),
CheckboxListTile(
title: Text('Modify advanced map settings'),
value: _advancedMapOptions,
onChanged: (newValue) => setState(() {
_advancedBTOptions = _advancedBTOptions;
_advancedMapOptions = newValue;
}),
controlAffinity: ListTileControlAffinity.leading,
),
Visibility(
visible: _advancedMapOptions,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text(
'Changing these settings can have serious performance impacts on your device and battery life',
style: TextStyle(
color: Colors.red[600],
fontSize: 12
),
textAlign: TextAlign.center,
),
),
),
_buildInputTile('Maximum other points', PreferencesHandler().getPreferencesInt('MAP.PROX.MAX_PER_QUERY'), _advancedMapOptions, TextInputType.number, <TextInputFormatter>[ WhitelistingTextInputFormatter.digitsOnly ], RegExp(r'\d+')),
_buildInputTile('Maximum age of other points (seconds)', PreferencesHandler().getPreferencesInt('MAP.PROX.MAX_AGE_SECONDS'), _advancedMapOptions, TextInputType.number, <TextInputFormatter>[ WhitelistingTextInputFormatter.digitsOnly ], RegExp(r'\d+')),
RaisedButton(
onPressed: () {
print(_formKey.currentState.validate());
if (_formKey.currentState.validate()) {
PreferencesHandler().setPreferencesBool('BT.USING_ADVANCED', _advancedBTOptions);
PreferencesHandler().setPreferencesString('BT.SERVICE_UUID', _bleServiceController.text);
PreferencesHandler().setPreferencesString('BT.CHARACTERISTIC_UUID', _bleCharacteristicController.text);
PreferencesHandler().setPreferencesBool('MAP.USING_ADVANCED', _advancedMapOptions);
}
},
child: Text('Validate Form'),
),
],
)
);
}
Widget _buildInputTile(String labelText, currentValue, bool enabled, TextInputType inputType, List<TextInputFormatter> inputFormatters, RegExp validation) =>
Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: TextFormField(
initialValue: currentValue.toString(),
enabled: enabled,
decoration: InputDecoration(
labelText: labelText,
contentPadding: EdgeInsets.zero,
),
keyboardType: inputType,
inputFormatters: inputFormatters,
validator: (value) {
if (value.isEmpty) {
return 'The field cannot be empty';
}
RegExpMatch match = validation.firstMatch(value);
if (match != null && match.input != value) {
return 'This is an invalid value';
}
return null;
}
)
);
}
\ No newline at end of file
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
......@@ -9,11 +8,11 @@ import 'package:logair_application/logic/handlers/position_handler.dart';
import 'package:logair_application/logic/data_packet.dart';
import 'package:logair_application/ui/carousel_card.dart';
import 'package:logair_application/ui/components/graph/graph.dart';
import 'package:logair_application/ui/components/graph/indicator.dart';
class DataWidget extends StatelessWidget {
DataWidget();
// TODO Remove
Text _buildText(String text, {FontWeight weight = FontWeight.normal, double size: 12}) {
return Text(
text,
......@@ -26,12 +25,15 @@ class DataWidget extends StatelessWidget {
);
}
// TODO Remove
TableRow _buildRow(List<Widget> children) {
return TableRow(
children: children.map((x) => Container(decoration: BoxDecoration(border: Border.all(color: Colors.black)), padding: EdgeInsets.symmetric(vertical: 4), child: x)).toList()
);
}
// TODO Remove
/// Displays packets ungracefully.
Widget _buildPacketDisplay(DataPacket packet) {
FontWeight b = FontWeight.bold;
return Column(
......@@ -57,15 +59,6 @@ class DataWidget extends StatelessWidget {
);
}
Widget _buildLocationDisplay(Position data) {
return Table(
children: [
_buildRow([_buildText('Latitude'), _buildText('Longitude')],),
_buildRow([_buildText((data != null) ? '${data.latitude}' : 'N/A'), _buildText((data != null) ? '${data.longitude}' : 'N/A')],),
]
);
}
@override
Widget build(BuildContext context) {
Widget widget = Center(
......@@ -92,7 +85,7 @@ class DataWidget extends StatelessWidget {
stream: PositionHandler().getCurrentOrLastPosition(),
builder: (context, snapshot) {
Position data = snapshot.data;
return _buildLocationDisplay(data);
return Text('Latitude ${data.latitude} | Longitude ${data.longitude}');
},
),
],
......
......@@ -3,9 +3,7 @@ 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/ui/animation/forward_animation.dart';
import 'package:logair_application/logic/controllers/map_display_controller.dart';
import 'package:logair_application/ui/routes/map_settings.dart';
import 'package:logair_application/ui/base_widget.dart';
import 'package:logair_application/ui/carousel_card.dart';
......@@ -193,19 +191,6 @@ class MapContainer extends BaseWidget {
],
),
),
Container(
padding: EdgeInsets.all(3),
width: 50,
height: 50,
child: FloatingActionButton(
heroTag: "buttonMapSettings",
onPressed: () => Navigator.push(context, PopForwardRoute(page: MapSettingsView())),
backgroundColor: Colors.grey[400],
child: Icon(
Icons.settings
)
)
),
StreamBuilder(
stream: MapDisplayController().isManualMotionUsed(),
initialData: false,
......
......@@ -2,10 +2,11 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/ui/base_widget.dart';
import 'package:logair_application/ui/components/map_settings_form.dart';
import 'package:logair_application/ui/components/general_settings_form.dart';
class MapSettingsView extends StatelessWidget {
MapSettingsView({Key key}) : super(key: key);
/// This [Widget] acts as a container for the [SettingsView] itself.
class SettingsView extends StatelessWidget {
SettingsView({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
......@@ -13,7 +14,7 @@ class MapSettingsView extends StatelessWidget {
builder: (context, sizingInfo) => SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Map Settings'),
title: Text('App Settings'),
),
body: Center(
child: Column(
......@@ -24,7 +25,7 @@ class MapSettingsView extends StatelessWidget {
builder: (context, sizingInfo) => Container(
width: double.infinity,
height: double.infinity,
child: MapSettingsForm()
child: SettingsForm()
),
),
),
......
......@@ -11,8 +11,7 @@ import 'package:logair_application/ui/data_widget.dart';
import 'package:logair_application/ui/social_widget.dart';
class HomeView extends StatelessWidget {
HomeView({Key key}) : super(key: key) {
}
HomeView({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
......
......@@ -4,12 +4,12 @@ import 'package:logair_application/logic/data_packet.dart';
import 'package:logair_application/logic/handlers/database_handler.dart';
import 'package:logair_application/ui/animation/forward_animation.dart';
import 'package:logair_application/ui/base/header.dart';
import 'package:logair_application/ui/routes/general_settings.dart';
import 'package:logair_application/utils/enums/bluetooth_connection_status.dart';
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/logic/handlers/bluetooth_wake_handler.dart';
import 'package:logair_application/logic/handlers/data_handler.dart';
import 'package:logair_application/logic/handlers/network_handler.dart';
import 'package:logair_application/ui/routes/map_settings.dart';
import 'package:logair_application/ui/base_widget.dart';
......@@ -33,19 +33,24 @@ class StatsView extends StatelessWidget {
height: double.infinity,
child: Column(
children: [
/// DEBUG START
RaisedButton(
onPressed: () =>
DatabaseHandler().insertData(DataPacket.fromData(1594212101606, 46.1665322, 5.9047703, 562, 208, 0.0170595515519381, 24.7, 95948.8, 40.6, null, 12, null, 14, "")),
DatabaseHandler().insertData(DataPacket.fromData(1594212101606, 46.1665322, 5.9047703, 562, 0.0170595515519381, 208, 24.7, 95948.8, 40.6, null, 12, null, 14, ""), 'logair1234'),
child: Text('Add to DB'),
),
RaisedButton(
onPressed: () => DatabaseHandler().data.then((value) => print(value)),
onPressed: () => DatabaseHandler().data.then((value) => print(value.join("\n"))),
child: Text('Print DB'),
),
RaisedButton(
onPressed: () => DatabaseHandler().deleteAll(),
onPressed: () => DatabaseHandler().recreateDB(),
child: Text('Delete DB'),
),
RaisedButton(
onPressed: () => DatabaseHandler().getUnsent(20).then((value) => value.forEach(print)),
child: Text('Print unsent'),
),
RaisedButton(
onPressed: () => BTWakeHandler().switchState(),
child: Text('Toggle BTLE Service'),
......@@ -59,29 +64,25 @@ class StatsView extends StatelessWidget {
onPressed: () => DataHandler().clearData(),
child: Text('Clear Data'),
),
/// DEBUG END
Spacer( flex: 1, ),
StreamBuilder<BluetoothConnectionStatus>(
initialData: null,
stream: BTLEHandler().getConnectionStatus(),
builder: (context, snapshot) {
BluetoothConnectionStatus data = snapshot.data;
return Text(data.toString());
},
),
Spacer( flex: 1, ),
/// Access the app settings
Container(
padding: EdgeInsets.all(3),
width: 50,
height: 50,
child: FloatingActionButton(
heroTag: "buttonSettings",
onPressed: () => Navigator.push(context, PopForwardRoute(page: MapSettingsView())),
onPressed: () => Navigator.push(context, PopForwardRoute(page: SettingsView())),
backgroundColor: Colors.grey[400],
child: Icon(
Icons.settings
)
)
),
/// Display whether or not the device is connected
/// TODO Replace with blinking light
StreamBuilder(
stream: BTLEHandler().isDeviceConnected(),
initialData: false,
......@@ -94,6 +95,21 @@ class StatsView extends StatelessWidget {
},
),
Spacer(flex: 1),
// Display generic database info
Container(
height: 50,
alignment: Alignment.center,
width: sizingInfo.localWidgetSize.width,
color: Colors.grey[200],
child: StreamBuilder(
stream: DatabaseHandler().getDBSize() ,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot snapshot){
return Text('Stored ${snapshot.data} packets in DB');
},
)
),
// Display Server connection info (//TODO blinking lights + text)
Container(
height: 50,
alignment: Alignment.center,
......@@ -123,7 +139,7 @@ class StatsView extends StatelessWidget {
Expanded(
flex: 1,
child: StreamBuilder<int>(
stream: DataHandler().getUnsentPacketsLength(),
stream: DatabaseHandler().getUnsentPacketsLength(),
initialData: 0,
builder: (context, snapshot) => Text(
'Unsent: ${snapshot.data}'
......@@ -138,6 +154,7 @@ class StatsView extends StatelessWidget {
),
),
),
/// Header Widget with battery status
Expanded(
flex: 1,
child: Listener(
......
double parseDoubleWrapper(String data) {
double result = double.tryParse(data);
if (result == null) {
int result2 = int.tryParse(data);
if (result2 == null)
return null;
else
result = result2.toDouble();
}
return result;
}
\ No newline at end of file
double result = double.tryParse(data);
if (result == null) {
int result2 = int.tryParse(data);
if (result2 == null)
return null;
else
result = result2.toDouble();
}
return result;
}
\ No newline at end of file
......@@ -57,13 +57,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
charcode:
dependency: transitive
description:
......@@ -84,7 +77,7 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.13"
version: "1.14.12"
console_log_handler:
dependency: transitive
description:
......@@ -328,7 +321,7 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.8"
version: "0.12.6"
meta:
dependency: transitive
description:
......@@ -343,6 +336,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.6+3"
multi_server_socket:
dependency: transitive
description:
name: multi_server_socket
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
nested:
dependency: transitive
description:
......@@ -620,21 +620,21 @@ packages:
name: test
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.1"
version: "1.14.4"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.17"
version: "0.2.15"
test_core:
dependency: transitive
description:
name: test_core
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.9"
version: "0.3.4"
transparent_image:
dependency: transitive
description:
......@@ -655,7 +655,7 @@ packages:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.1.6"
uuid:
dependency: "direct main"
description:
......@@ -713,5 +713,5 @@ packages:
source: hosted
version: "2.2.0"
sdks:
dart: ">=2.9.0-14.0.dev <3.0.0"
dart: ">=2.7.0 <3.0.0"
flutter: ">=1.12.13+hotfix.6 <2.0.0"
......@@ -33,6 +33,7 @@ dependencies:
sqflite:
path:
intl: ^0.16.1
intl_translation: ^0.17.9
......