...
 
Commits (7)
{"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-05-15 10:37:30.582661","version":"1.19.0-2.0.pre.71"}
\ 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
......@@ -46,6 +46,10 @@ Integrated Geolocation to the app and visualization on the app.
### [0.0.6] Added
- (UI) Map
- (UI) Settings
- ()
### [0.0.6] Changed
- `BTLEHandler`
......
......@@ -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
......@@ -6,6 +6,7 @@ import 'package:logair_application/utils/utils.dart';
/// and contains methods for:
/// quick lookup,
/// conversion to POST format,
// TODO Firmware@v2 : Extra data
class DataPacket {
List<int> _data;
int _timestamp;
......@@ -14,7 +15,7 @@ class DataPacket {
double _latitude;
double _longitude;
int _altitude;
double _heading;
int _heading;
double _speed;
/* Ambient metrics */
......@@ -28,11 +29,14 @@ class DataPacket {
double _pm4;
double _pm10;
// TODO Firmware v2
String _extraData;
int timestamp() => _timestamp;
double latitude() => _latitude;
double longitude() => _longitude;
int altitude() => _altitude;
double heading() => _heading;
int heading() => _heading;
double speed() => _speed;
double temperature() => _temperature;
double pressure() => _pressure;
......@@ -48,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;
......@@ -66,11 +70,30 @@ 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._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}';
......@@ -90,4 +113,28 @@ class DataPacket {
this._pm4,
this._pm10
];
Map<String, dynamic> toMap() {
return {
'timestamp_nix': this._timestamp,
'latitude': this._latitude,
'longitude': this._longitude,
'altitude': this._altitude,
'speed': this._speed,
'heading': this._heading,
'temperature': this._temperature,
'relative_humidity': this._temperature,
'pressure': this._pressure,
'pm_1': this._pm1,
'pm_2_5': this._pm2_5,
'pm_4': this._pm4,
'pm_10': this._pm10,
'extra': ''
};
}
@override
String toString() {
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(
join((await getDatabasesPath()), 'logair_data.db'),
version: 1,
onCreate: (db, version) => db.execute('''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
)''')
);
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, String deviceId) async {
final Database database = await _database;
Map<String, dynamic> insert = packet.toMap();
insert['device_id'] = deviceId;
await database.insert(
'data',
insert,
conflictAlgorithm: ConflictAlgorithm.replace
);
}
Future<List<DataPacket>> get data async {
final Database database = await _database;
final List<Map<String, dynamic>> data = await database.query('data');
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
......@@ -3,13 +3,13 @@ import 'dart:convert';
import 'dart:collection';
import 'package:flutter_map/flutter_map.dart';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
import 'package:latlong/latlong.dart';
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.
......@@ -17,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());
......@@ -28,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';
......@@ -38,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 = {
......@@ -64,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 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:logair_application/logic/controllers/home_controller.dart';
import 'package:logair_application/logic/controllers/map_display_controller.dart';
import 'package:logair_application/logic/handlers/data_handler.dart';
import 'package:logair_application/logic/handlers/network_handler.dart';
import 'package:logair_application/logic/handlers/position_handler.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/ui/routes/home.dart';
import 'package:logair_application/localization/localization_delegate.dart';
......@@ -26,16 +17,6 @@ void main() {
runApp(displayMode ? DevicePreview(child: LogAirApplication()) : LogAirApplication());
}
Future<void> _startHandlers() async {
HomeController();
BTLEHandler();
NetworkHandler();
DataHandler();
PreferencesHandler();
PositionHandler();
MapDisplayController();
}
class LogAirApplication extends StatelessWidget {
static const String _title = 'LogAir Connector';
......
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/services/battery_service.dart';
import 'package:logair_application/ui/base/custom_paint.dart';
import 'dart:math';
class BatteryVisualizerWidget extends StatelessWidget {
BatteryVisualizerWidget(this.isLeft, {Key key}) : super(key: key);
Stream<int> getBatteryLevel2() async* {
Random r = new Random();
while (true) {
yield 100; //r.nextInt(100);
await Future.delayed(Duration(seconds: 10));
}
}
final bool isLeft;
Color getColor(int value) {
if (value <= 10)
return Colors.red;
else if (10 < value && value <= 20)
return Colors.yellow;
else if (20 < value)
return Colors.green;
else
return Colors.grey[100];
}
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: this.isLeft ? BatteryService().getBatteryLevel() : getBatteryLevel2(),
initialData: 0,
builder: (context, snapshot) {
int data = snapshot.data;
return Stack(
children: [
CustomPaint(
size: Size.infinite,
painter: CardPainter(isLeft: this.isLeft, battery: data, color: getColor(data))
),
Container(
alignment: Alignment.center,
child: Text("Battery $data"),
)
]
);
}
);
}
}
\ No newline at end of file
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/ui/animation/forward_animation.dart';
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/ui/bluetooth_dialog/dialog.dart';
import 'package:logair_application/utils/enums/bluetooth_connection_status.dart';
/// Button enabling the app to connect to a PM detector device.
class BluetoothButton extends StatefulWidget {
BluetoothButton({Key key}) : super(key: key);
@override
_BluetoothButtonState createState() => _BluetoothButtonState();
}
class _BluetoothButtonState extends State<BluetoothButton> {
_BluetoothButtonState({Key key});
Color _color;
BluetoothConnectionStatus _status;
// Can ignore dart(cancel_subscriptions) because linter error, subscription canceled in dispose.
// ignore: cancel_subscriptions
StreamSubscription<BluetoothConnectionStatus> _connectionStatusListener;
/// This [Function] enables non-dirty transitions, only on value change, reducing overhead.
void connectionStatusStateChanger(BluetoothConnectionStatus newStatus) {
if (mounted && newStatus != _status) {
Color newColor;
switch (newStatus) {
case BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING:
newColor = Colors.grey[350];
break;
case BluetoothConnectionStatus.BTSTATUS_DISCOVERING_SERVICES:
newColor = Colors.blue[100];
break;
case BluetoothConnectionStatus.BTSTATUS_GETTING_CHARACTERISTICS:
newColor = Colors.blue[200];
break;
case BluetoothConnectionStatus.BTSTATUS_SETTING_MTU:
newColor = Colors.blue[300];
break;
case BluetoothConnectionStatus.BTSTATUS_STREAMING:
newColor = Colors.blueAccent;
break;
default:
}
setState(() {
_color = newColor;
_status = newStatus;
});
}
}
@override
void initState() {
super.initState();
this._connectionStatusListener = BTLEHandler().getConnectionStatus().listen(connectionStatusStateChanger);
}
@override
Future<void> dispose() async {
this._connectionStatusListener.cancel();
this._connectionStatusListener = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: StreamBuilder<BluetoothConnectionStatus>(
stream: BTLEHandler().getConnectionStatus(),
initialData: BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING,
builder: (context, snapshot) {
BluetoothConnectionStatus _bluetoothStatus = snapshot.data;
return Ink(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2.0,
),
shape: BoxShape.circle,
),
child: Ink(
decoration: BoxDecoration(
border: Border.all(
color: Colors.indigoAccent,
width: 2.0,
),
color: _color,
shape: BoxShape.circle,
),
child: InkWell(
borderRadius: BorderRadius.circular(100.0),
onTap: () {
print("onTap");
if (_bluetoothStatus != BluetoothConnectionStatus.BTSTATUS_STREAMING) {
print("Tap On");
Navigator.push(
context,
PopForwardRoute(
page: BluetoothDialog(),
),
);
return true;
} else {
BTLEHandler().disconnect();
print('Tap off');
return true;
}
},
child: Padding(
padding: EdgeInsets.all(10),
child: Icon(
Icons.bluetooth,
color: Colors.white,
size: 30.0,
),
),
)
)
);
}
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
class CardPainter extends CustomPainter {
CardPainter({@required this.isLeft, @required this.battery, @required this.color});
final bool isLeft;
int battery;
Color color;
setColor(Color newColor) {
this.color = newColor;
}
@override
void paint(Canvas canvas, Size size) {
final shapeBounds = Rect.fromLTRB(0, 0, size.width, size.height);
_drawBackground(canvas, shapeBounds);
}
@override
bool shouldRepaint(CardPainter oldDelegate) {
return color != oldDelegate.color;
}
void _drawBackground(Canvas canvas, Rect shapeBounds) {
final Paint paint = Paint()..color = color;
final rectWidth = shapeBounds.width * this.battery / 100;
final backgroundPath = (this.isLeft) ?
(Path()
..moveTo(shapeBounds.left, shapeBounds.top)
..lineTo(shapeBounds.bottomLeft.dx, shapeBounds.bottomLeft.dy)
..lineTo(shapeBounds.bottomLeft.dx + rectWidth, shapeBounds.bottomRight.dy)
..lineTo(shapeBounds.bottomLeft.dx + rectWidth, shapeBounds.topRight.dy) //7
..close()) :
(Path()
..moveTo(shapeBounds.right, shapeBounds.top) //3
..lineTo(shapeBounds.bottomRight.dx, shapeBounds.bottomRight.dy)
..lineTo(shapeBounds.bottomRight.dx - rectWidth, shapeBounds.bottomRight.dy)
..lineTo(shapeBounds.topRight.dx - rectWidth, shapeBounds.topRight.dy) //7
..close()); //8
canvas.drawPath(backgroundPath, paint);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:logair_application/ui/base/battery_visualizer.dart';
import 'package:logair_application/ui/base/bluetooth_button.dart';
class HeaderWidget extends StatelessWidget {
HeaderWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Row(
children: [
Expanded(
child: BatteryVisualizerWidget(true),
),
Expanded(
child: BatteryVisualizerWidget(false),
)
],
),
BluetoothButton(),
],
);
}
}
\ No newline at end of file
......@@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/ui/components/bluetooth_adapter_off.dart';
import 'package:logair_application/ui/components/bluetooth_adapter_unavailable.dart';
import 'package:logair_application/ui/dialog/bluetooth_selection_dialog.dart';
import 'package:logair_application/ui/bluetooth_dialog/bluetooth_adapter_off.dart';
import 'package:logair_application/ui/bluetooth_dialog/adapter_unavailable.dart';
import 'package:logair_application/ui/bluetooth_dialog/selection_dialog.dart';
class BluetoothDialog extends StatefulWidget {
BluetoothDialog({Key key}) : super(key: key);
......@@ -61,10 +61,12 @@ class _BluetoothDialogState extends State<BluetoothDialog> {
),
Expanded(
flex: 1,
child: RaisedButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text("Back"),
)
child: Center(
child: RaisedButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text("Back"),
),
),
),
],
),
......
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:logair_application/localization/localization.dart';
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/ui/base_widget.dart';
import 'package:logair_application/ui/components/bt_scan_tile.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
class Device {
final String name;
final String address;
Device({this.name, this.address});
Device.fromJson(Map<String, dynamic> data) : name = data["name"], address = data["address"];
@override
String toString() => 'DEVICE $name@$address';
}
class BluetoothSelectionDialog extends StatefulWidget {
BluetoothSelectionDialog({Key key}) : super(key: key);
@override
_BluetoothSelectionDialogState createState() => _BluetoothSelectionDialogState();
}
class _BluetoothSelectionDialogState extends State<BluetoothSelectionDialog> {
_BluetoothSelectionDialogState();
List<Device> _frequentDevices;
@override
Widget build(BuildContext context) {
if (_frequentDevices != null)
setState(
() => this._frequentDevices = List.from(
json.decode((PreferencesHandler().getPreferencesString('BT.FREQUENT') ?? '[ ]'))
.map((device) => Device(name: device["name"], address: device["address"]))));
return BaseWidget(
builder: (context, sizingInfo) => Container(
height: sizingInfo.screenSize.height * 0.9,
child: RefreshIndicator(
onRefresh: () => BTLEHandler().fblueInstance.startScan(),
child: SingleChildScrollView(
padding: EdgeInsets.all(0),
child: Container(
child: Column(
children: <Widget>[
ListTile(
leading: IconButton(
padding: EdgeInsets.all(0),
icon: Icon(Icons.arrow_back),
onPressed: () async {
if (await BTLEHandler().fblueInstance.isScanning.first)
BTLEHandler().fblueInstance.stopScan();
Navigator.of(context).pop(false);
},
),
title: Text(
AppLocalization.of(context).bluetoothScanMessage,
style: Theme.of(context).textTheme.headline6,
textAlign: TextAlign.center,
),
dense: true,
trailing: IconButton(
padding: EdgeInsets.all(0),
icon: Icon(Icons.refresh),
onPressed: () => BTLEHandler().fblueInstance.startScan(timeout: Duration(seconds: 10)),
),
),
Divider(),
Text(
AppLocalization.of(context).bluetoothFrequent
),
StreamBuilder<List<ScanResult>>(
stream: BTLEHandler().fblueInstance.scanResults.transform(
new StreamTransformer.fromHandlers(
handleData: (List<ScanResult> list, EventSink<List<ScanResult>> sink) =>
sink.add(list.where((result) => _frequentDevices != null ? _frequentDevices.map((Device d) => d.address).contains(result.device.id.toString()) : false).toList())
)
),
initialData: [],
builder: (context, snapshot) => Column(
children: (snapshot.data.length > 0) ?
snapshot.data.map(
(result) => BTLEScanTile(
result: result,
onTap: () {
BTLEHandler().setDevice(result.device);
BTLEHandler().connect();
Navigator.of(context).pop(true);
}
),
).toList() : <Widget>[
Text(
AppLocalization.of(context).none,
style: Theme.of(context).textTheme.caption,
)
],
)
),
Divider(),
Text(
AppLocalization.of(context).bluetoothAvailable
),
StreamBuilder<List<ScanResult>>(
stream: BTLEHandler().fblueInstance.scanResults.transform(
new StreamTransformer.fromHandlers(
handleData: (List<ScanResult> list, EventSink<List<ScanResult>> sink) =>
sink.add(
list.where(
(result) =>
((_frequentDevices != null) ?
!_frequentDevices.map((Device d) => d.address).contains(result.device.id.toString())
: true) && result.device.name.length > 0).toList())
)
),
initialData: [],
builder: (context, snapshot) => Column(
children: (snapshot.data.length > 0) ?
snapshot.data.map(
(result) => BTLEScanTile(
result: result,
onTap: () {
BTLEHandler().fblueInstance.stopScan();
BTLEHandler().setDevice(result.device);
BTLEHandler().connect();
Navigator.of(context).pop(true);
}
),
).toList() : <Widget>[
Text(
"None",
style: Theme.of(context).textTheme.caption,
)
],
)
),
],
),
),
),
),
),
);
}
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:logair_application/localization/localization.dart';
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/ui/base_widget.dart';
import 'package:logair_application/ui/components/bt_scan_tile.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
class Device {
final String name;
final String address;
Device({this.name, this.address});
Device.fromJson(Map<String, dynamic> data) : name = data["name"], address = data["address"];
@override
String toString() => 'DEVICE $name@$address';
}
class BluetoothSelectionDialog extends StatefulWidget {
BluetoothSelectionDialog({Key key}) : super(key: key);
@override
_BluetoothSelectionDialogState createState() => _BluetoothSelectionDialogState();
}
class _BluetoothSelectionDialogState extends State<BluetoothSelectionDialog> {
_BluetoothSelectionDialogState();
List<Device> _frequentDevices;
@override
Widget build(BuildContext context) {
if (_frequentDevices != null)
setState(
() => this._frequentDevices = List.from(
json.decode((PreferencesHandler().getPreferencesString('BT.FREQUENT') ?? '[ ]'))
.map((device) => Device(name: device["name"], address: device["address"]))));
return BaseWidget(
builder: (context, sizingInfo) => Container(
height: sizingInfo.screenSize.height * 0.9,
child: RefreshIndicator(
onRefresh: () => BTLEHandler().fblueInstance.startScan(),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.all(0),
child: Container(
child: Column(
children: <Widget>[
ListTile(
leading: IconButton(
padding: EdgeInsets.all(0),
icon: Icon(Icons.arrow_back),
onPressed: () async {
if (await BTLEHandler().fblueInstance.isScanning.first)
BTLEHandler().fblueInstance.stopScan();
Navigator.of(context).pop(false);
},
),
title: Text(
AppLocalization.of(context).bluetoothScanMessage,
style: Theme.of(context).textTheme.headline6,
textAlign: TextAlign.center,
),
dense: true,
trailing: IconButton(
padding: EdgeInsets.all(0),
icon: Icon(Icons.refresh),
onPressed: () async {
if (await BTLEHandler().fblueInstance.isScanning.first)
BTLEHandler().fblueInstance.stopScan();
try {
BTLEHandler().fblueInstance.startScan(timeout: Duration(seconds: 10));
} catch (e) {
}
},
),
),
Divider(),
Text(
AppLocalization.of(context).bluetoothFrequent
),
StreamBuilder<List<ScanResult>>(
stream: BTLEHandler().fblueInstance.scanResults.transform(
new StreamTransformer.fromHandlers(
handleData: (List<ScanResult> list, EventSink<List<ScanResult>> sink) =>
sink.add(list.where((result) => _frequentDevices != null ? _frequentDevices.map((Device d) => d.address).contains(result.device.id.toString()) : false).toList())
)
),
initialData: [],
builder: (context, snapshot) => Column(
children: (snapshot.data.length > 0) ?
snapshot.data.map(
(result) => BTLEScanTile(
result: result,
onTap: () {
BTLEHandler().setDevice(result.device);
BTLEHandler().connect();
Navigator.of(context).pop(true);
}
),
).toList() : <Widget>[
Text(
AppLocalization.of(context).none,
style: Theme.of(context).textTheme.caption,
)
],
)
),
Divider(),
Text(
AppLocalization.of(context).bluetoothAvailable
),
StreamBuilder<List<ScanResult>>(
stream: BTLEHandler().fblueInstance.scanResults.transform(
new StreamTransformer.fromHandlers(
handleData: (List<ScanResult> list, EventSink<List<ScanResult>> sink) =>
sink.add(
list.where(
(result) =>
((_frequentDevices != null) ?
!_frequentDevices.map((Device d) => d.address).contains(result.device.id.toString())
: true) && result.device.name.length > 0).toList())
)
),
initialData: [],
builder: (context, snapshot) => Column(
children: (snapshot.data.length > 0) ?
snapshot.data.map(
(result) => BTLEScanTile(
result: result,
onTap: () {
BTLEHandler().fblueInstance.stopScan();
BTLEHandler().setDevice(result.device);
BTLEHandler().connect();
Navigator.of(context).pop(true);
}
),
).toList() : <Widget>[
Text(
"None",
style: Theme.of(context).textTheme.caption,
)
],
)
),
],
),
),
),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/services/battery_service.dart';
class LocalBatteryVisualizerWidget extends StatelessWidget {
LocalBatteryVisualizerWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: BatteryService().getBatteryLevel(),
initialData: null,
builder: (context, snapshot) {
int data = snapshot.data;
return Container(color: Colors.blue.shade100, alignment: Alignment.center, child: Text("Battery $data"),);
},
);
}
}
\ No newline at end of file
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/ui/animation/forward_animation.dart';
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/ui/dialog/bluetooth_dialog.dart';
class BluetoothButton extends StatefulWidget {
BluetoothButton({Key key}) : super(key: key);
@override
_BluetoothButtonState createState() => _BluetoothButtonState();
}
class _BluetoothButtonState extends State<BluetoothButton> {
_BluetoothButtonState({Key key});
@override
Widget build(BuildContext context) {
return Material(
child: StreamBuilder<bool>(
stream: BTLEHandler().isDeviceConnected(),
initialData: false,
builder: (context, snapshot) {
bool _bluetoothConnected = snapshot.data;
return Ink(
decoration: BoxDecoration(
border: Border.all(
color: Colors.indigoAccent,
width: 2.0,
),
color: _bluetoothConnected ? Colors.blue[200] : Colors.white,
shape: BoxShape.circle,
),
child: InkWell(
borderRadius: BorderRadius.circular(100.0),
onTap: () {
print("onTap");
if (!_bluetoothConnected) {
print("Tap On");
Navigator.push(
context,
PopForwardRoute(
page: BluetoothDialog(),
),
);
return true;
} else {
BTLEHandler().disconnect();
print('Tap off');
return true;
}
},
child: Padding(
padding: EdgeInsets.all(10),
child: Icon(
Icons.bluetooth,
color: _bluetoothConnected ? Colors.grey : Colors.red,
size: 30.0,
),
),
)
);
}
),
);
}
}
\ 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(