Commit 006af31e authored by Nicolas Richard Walter Boeckh's avatar Nicolas Richard Walter Boeckh 💬
Browse files

Fixed some settings, integrated some others. ¯\_(ツ)_/¯

parent 1ed68f5f
{"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-09-02 01:23:48.043026","version":"1.21.0-10.0.pre.193"}
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"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-09-02 16:14:32.437550","version":"1.22.0-2.0.pre.18"}
\ No newline at end of file
......@@ -4,13 +4,18 @@ import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:tuple/tuple.dart';
/// Handler that integrates a mysqli database into the application's workflow.
class DatabaseHandler {
factory DatabaseHandler() => _singleton;
Future<Database> _db;
DatabaseHandler._internal();
bool shouldCache;
static final DatabaseHandler _singleton = new DatabaseHandler._internal();
/// Reference to the internalized [Database] within this instance.
Future<Database> _db;
/// [Database] initialization string, to reduce repetition.
final String createString = '''CREATE TABLE data(
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT,
......@@ -32,6 +37,7 @@ class DatabaseHandler {
exported TINYINT DEFAULT 0
)''';
/// Retrieves the internal [Database] instance, and if not instantiated, instantiates it.
Future<Database> get _database async {
if (_db == null)
this._db = openDatabase(
......@@ -42,6 +48,7 @@ class DatabaseHandler {
return this._db;
}
/// Purges the [Database] of all internal.
Future<void> recreateDB() async {
final Database database = await _database;
......@@ -49,10 +56,7 @@ class DatabaseHandler {
await database.rawQuery(createString);
}
DatabaseHandler._internal();
static final DatabaseHandler _singleton = new DatabaseHandler._internal();
/// Inserts data into the [Database].
Future<void> insertData(DataPacket packet, String deviceId, String url) async {
final Database database = await _database;
Map<String, dynamic> insert = packet.toMap();
......@@ -65,6 +69,7 @@ class DatabaseHandler {
);
}
/// Retrieves all of the [DataPacket]s inserted into the [Database].
Future<List<DataPacket>> get data async {
final Database database = await _database;
......@@ -73,8 +78,10 @@ class DatabaseHandler {
return List.generate(data.length, (i) => DataPacket.fromMappedData(data[i]));
}
/// Closes the [Database] instance.
Future<void> close() async => (await this._database).close();
/// Streams the amount of elements contained within the [Database].
Stream<int> getDBSize() async* {
final Database database = await _database;
while (true) {
......@@ -84,6 +91,7 @@ class DatabaseHandler {
}
}
/// Retrieves all of the unsent packets in the [DataPacket].
Future<List<Tuple2<int, DataPacket>>> getUnsent(int limit) async {
final Database database = await _database;
final List<Map<String, dynamic>> data = await database.query('data',
......@@ -93,8 +101,6 @@ class DatabaseHandler {
orderBy: 'timestamp_nix ASC'
);
print(data);
return List.generate(data.length, (i) => Tuple2(data[i]['id'], DataPacket.fromMappedData(data[i])));
}
......@@ -121,17 +127,17 @@ class DatabaseHandler {
}
}
/// Retrieves the latest 5 seconds of data every 5 seconds.
Stream<Map<String, dynamic>> getLatest() async* {
final Database database = await _database;
while (true) {
// TODO Change query to timestamp - x time
final timestampLimit = DateTime.now().millisecondsSinceEpoch - 10000;
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT MIN(timestamp_nix) AS start, AVG(pm_1) AS pm_1, AVG(pm_2_5) AS pm_2_5, AVG(pm_4) AS pm_4, AVG(pm_10) AS pm_10 FROM (SELECT * FROM data WHERE timestamp_nix >= $timestampLimit ORDER BY id DESC LIMIT 5)');
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT MIN(timestamp_nix) AS start, AVG(pm_1) AS pm_1, AVG(pm_2_5) AS pm_2_5, AVG(pm_4) AS pm_4, AVG(pm_10) AS pm_10 FROM (SELECT * FROM data WHERE timestamp_nix >= ${DateTime.now().millisecondsSinceEpoch - 10000} ORDER BY id DESC LIMIT 5)');
yield data[0] ?? Map();
await Future.delayed(Duration(seconds: 5));
}
}
/// Retrieves the latest [maxResults] packets inserted into the [Database].
Future<List<DataPacket>> getLastPackets(int maxResults) async {
final Database database = await _database;
......
......@@ -5,18 +5,26 @@ import 'package:shared_preferences/shared_preferences.dart';
class PreferencesHandler {
static final PreferencesHandler _singleton = new PreferencesHandler._internal();
factory PreferencesHandler() => _singleton;
PreferencesHandler._internal();
/// The singleton reference to the [SharedPreferences] instance.
SharedPreferences _sharedPreferencesInternal;
/// Whether the [SharedPreferences] have been initialized with default values.
bool _setup = false;
/// This enables the retrieval of the [SharedPreferences] ecosystem and setting it up if needed first.
Future<SharedPreferences> get _sharedPreferences async {
// If the assertion fails
if (!PreferenceKeys.runAssertion()) {
throw new Exception('A <defaultValue<T>, T> pair is mismatched');
}
if (_sharedPreferencesInternal == null && !this._setup) {
print('Sean Connery says: Shetup.');
/// If some of the [PreferenceKeys] have mismatched types set to their default values.
if (!PreferenceKeys.assertTypesMatch())
throw new Exception('A <defaultValue<T>, T> pair is mismatched');
this._sharedPreferencesInternal = await SharedPreferences.getInstance();
/// Initial Setup
PreferenceKeys.list.forEach((PreferenceKeys prefs) {
Function setter;
......@@ -45,18 +53,29 @@ class PreferencesHandler {
return this._sharedPreferencesInternal;
}
PreferencesHandler._internal();
/// Retrieves an [int] from the [SharedPreferences].
Future<int> getPreferencesInt(String key) async => (await this._sharedPreferences).getInt(key);
/// Retrieves a [String] from the [SharedPreferences].
Future<String> getPreferencesString(String key) async => (await this._sharedPreferences).getString(key);
/// Retrieves a [bool] from the [SharedPreferences].
Future<bool> getPreferencesBool(String key) async => (await this._sharedPreferences).getBool(key);
/// Retrieves a [dynamic] from the [SharedPreferences].
Future<dynamic> getPreferences(String key) async => (await this._sharedPreferences).get(key);
/// Sets an [int] within the [SharedPreferences].
Future<void> setPreferencesInt(String key, int value) async => (await this._sharedPreferences).setInt(key, value);
/// Sets a [String] within the [SharedPreferences].
Future<void> setPreferencesString(String key, String value) async => (await this._sharedPreferences).setString(key, value);
/// Sets a [bool] within the [SharedPreferences].
Future<void> setPreferencesBool(String key, bool value) async => (await this._sharedPreferences).setBool(key, value);
/// Resets a specific [SharedPreferences] value to it's default value.
Future<void> resetPreferences(String key) async {
Function setter;
PreferenceKeys pref = PreferenceKeys.get(key);
......@@ -74,15 +93,12 @@ class PreferencesHandler {
await setter(pref.key, pref.defaultValue);
}
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;
/// This is a generic function to reset all preferences to their default values.
void resetAllPreferences() async =>
PreferenceKeys.list.forEach(
// Necessary to avoid subtype erroneous type checking ¯\_(ツ)_/¯
(PreferenceKeys e) => (e) async {
await resetPreferences(e.key);
}
);
}
\ No newline at end of file
......@@ -7,7 +7,6 @@ import 'package:logair_application/logic/handlers/data_handler.dart';
import 'package:logair_application/ui/components/common/carousel_card.dart';
import 'package:logair_application/ui/components/body/data/graph/graph.dart';
import 'package:logair_application/ui/components/body/data/tiles/data_tile.dart';
import 'package:logair_application/ui/components/common/dash_separator.dart';
class DataWidget extends StatelessWidget {
DataWidget();
......@@ -49,12 +48,7 @@ class DataWidget extends StatelessWidget {
),
Column(
children: intersperse(
Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: DashSeparator(
color: Colors.grey[400],
),
),
Divider(indent: 10, endIndent: 10, color: Colors.black, height: 5,),
[
DataTile(stream: DataHandler().getMockDataStream(), locationName: 'Home'),
DataTile(stream: DataHandler().getMockDataStream(), locationName: 'Work'),
......
......@@ -4,8 +4,7 @@ import 'package:flutter/material.dart';
import 'package:logair_application/ui/components/body/data/graph/graph_colors.dart';
import 'package:logair_application/ui/components/body/data/graph/graph_controller.dart';
import 'package:logair_application/ui/components/body/data/graph/indicator.dart';
/// WIP
/// TODO Add axis titles
/// This graph displays the average quality of the air in 5 second increments.
class Graph extends StatefulWidget {
Graph({Key key}) : super(key: key);
......
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/logic/data_packet.dart';
import 'package:logair_application/ui/components/common/dash_separator.dart';
import 'package:logair_application/utils/enums/pm_symbol.dart';
//TODO Add weather ?
......@@ -32,8 +31,10 @@ class _DataTileState extends State<DataTile> {
/// [Stream] of [DataPacket] that tracks the newly received packets.
final Stream<DataPacket> stream;
/// Whether the data is the local data or data pooled from another location.
final bool isDistant;
/// The name of the location if the data has been pooled from there.
final String locationName;
/// Value of the PM1 metric.
......@@ -76,28 +77,28 @@ class _DataTileState extends State<DataTile> {
@override
Widget build(BuildContext context) {
/// This builds an indicator for a specific PM value, with appropriate AQI color scheme
Widget _buildPMDataCell(PMSymbol cellTitle, double value) => Expanded(
/// Builds an indicator for a specific PM value, with appropriate AQI color scheme
Widget _buildPMDataCell(PMSymbol pmSymbol, double value) => Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.all(2),
padding: EdgeInsets.symmetric(vertical: 3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: (value != -1 && value != null) ? cellTitle.colorizeValue(value) : Colors.white,
color: (value != -1 && value != null) ? pmSymbol.colorizeValue(value) : Colors.white,
),
child: Column(
children: [
Text(
cellTitle.key,
pmSymbol.key,
style: TextStyle(
fontSize: 14
fontSize: 19
),
),
SizedBox(height: 4),
Text(
(value != -1 && value != null) ? value.toString() : 'N/A',
style: TextStyle(
fontSize: 16,
fontSize: 17,
fontWeight: FontWeight.bold
),
)
......@@ -106,111 +107,105 @@ class _DataTileState extends State<DataTile> {
)
);
// TODO Doc
Widget _buildEnvironmentalDataCellHeaders(String title) => Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.all(2),
padding: EdgeInsets.symmetric(vertical: 3),
alignment: Alignment.center,
child: Text(
title,
style: TextStyle(
fontSize: 15,
),
),
)
);
// TODO Doc
Widget _buildEnvironmentalDataCells(double value) => Expanded(
/// Builds the headers indicator for environmental data cells
Widget _buildEnvironmentalDataCells(String title, String unit, double value) => Expanded(
flex: 1,
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.all(2),
padding: EdgeInsets.symmetric(vertical: 3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: (value != -1 && value != null) ? Colors.grey[200] : Colors.white,
color: Colors.grey[200],
),
child: Text(
(value != -1 && value != null) ? value.toString() : 'N/A',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold
),
child: Column(
children: [
Text(
title,
style: TextStyle(
fontSize: 20
),
),
Text(
unit,
style: TextStyle(
fontSize: 14
),
),
SizedBox(height: 8),
Text(
(value != -1 && value != null) ? value.toString() : 'N/A',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold
),
)
]
)
)
);
return Container(
margin: EdgeInsets.symmetric(horizontal: 3),
child: Column(
margin: EdgeInsets.symmetric(horizontal: 1, vertical: 3),
child: Row(
children: [
(this.isDistant != null && this.locationName != null && this.isDistant) ? (
Container(
padding: EdgeInsets.symmetric(vertical: 3),
child: Text(
'Location: ${this.locationName}',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold
)
)
)
) : Container(width: 0, height: 0),
Row(
children: [
Expanded(
flex: 1,
child: Column(
Expanded(
flex: 1,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildPMDataCell(PMSymbol.PM1, _pm1),
_buildPMDataCell(PMSymbol.PM2_5, _pm2_5),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildPMDataCell(PMSymbol.PM4, _pm4),
_buildPMDataCell(PMSymbol.PM10, _pm10),
],
),
_buildPMDataCell(PMSymbol.PM1, _pm1),
SizedBox(width: 2,),
_buildPMDataCell(PMSymbol.PM2_5, _pm2_5),
],
),
),
Expanded(
flex: 2,
child: Column(
SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.max,
children: [
_buildEnvironmentalDataCellHeaders('Temperature'),
_buildEnvironmentalDataCellHeaders('Pressure'),
_buildEnvironmentalDataCellHeaders('Relative\nHumidity'),
],
),
Row(
mainAxisSize: MainAxisSize.max,
children: [
_buildEnvironmentalDataCells(_temperature),
_buildEnvironmentalDataCells(_pressure),
_buildEnvironmentalDataCells(_relativeHumidity),
],
),
]
_buildPMDataCell(PMSymbol.PM4, _pm4),
SizedBox(width: 2,),
_buildPMDataCell(PMSymbol.PM10, _pm10),
],
),
),
],
],
),
),
Expanded(
flex: 2,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
(this.isDistant != null && this.locationName != null && this.isDistant) ? Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Text(
'${this.locationName}',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold
)
)
) : Container(),
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
_buildEnvironmentalDataCells('Temp.', '(C°)', _temperature),
_buildEnvironmentalDataCells('Pres.', '(hPa)', _pressure),
_buildEnvironmentalDataCells('Rel. Hum.', '(%)', _relativeHumidity),
],
),
]
)
),
],
),
]
)
);
}
}
......@@ -6,6 +6,7 @@ import 'package:logair_application/logic/handlers/database_handler.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
import 'package:logair_application/ui/components/common/custom_expansion_tile.dart';
import 'package:logair_application/ui/components/common/dash_separator.dart';
import 'package:logair_application/utils/enums/preference_keys.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.
......@@ -37,6 +38,8 @@ class _SettingsFormState extends State<SettingsForm> {
bool _advancedBTOptions;
bool _advancedMapOptions;
final _netQueryMaxPush = TextEditingController();
final _netQueryFrequency = TextEditingController();
final _bleServiceController = TextEditingController();
final _bleCharacteristicController = TextEditingController();
final _mapQueryLimitController = TextEditingController();
......@@ -75,8 +78,9 @@ class _SettingsFormState extends State<SettingsForm> {
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: _buildInputTile(_netQueryMaxPush, 'Maximum number of items per push', 'NET.MAX_ITEMS_PER_PUSH', TextInputType.number, <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ], RegExp(r'.*'))),
ListTile(title: _buildInputTile(_netQueryFrequency, 'Frequency between push attempts (seconds)', 'NET.TIME_TO_PUSH', TextInputType.number, <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ], RegExp(r'.*'))),
ListTile(title: Text('TODO: Push on 3G/4G')),
],
),
......@@ -118,8 +122,8 @@ class _SettingsFormState extends State<SettingsForm> {
),
),
// TODO Service / Characteristic Explorer ?
ListTile(title: _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}'))),
ListTile(title: _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}'))),
ListTile(title: _buildInputTile(_bleServiceController, 'BLE Service UUID', 'BT.SERVICE_UUID', 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}'), enabled: _advancedBTOptions)),
ListTile(title: _buildInputTile(_bleCharacteristicController, 'BLE Characteristic UUID', 'BT.CHARACTERISTIC_UUID', 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}'), enabled: _advancedBTOptions)),
],
),
// MAP
......@@ -154,8 +158,8 @@ class _SettingsFormState extends State<SettingsForm> {
),
),
),
ListTile(title: _buildInputTile(_mapQueryLimitController, 'Maximum other points', 'MAP.PROX.MAX_PER_QUERY', _advancedMapOptions, TextInputType.number, <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ], RegExp(r'\d+'))),
ListTile(title: _buildInputTile(_mapQueryAgeController, 'Maximum age of other points (seconds)', 'MAP.PROX.MAX_AGE_SECONDS', _advancedMapOptions, TextInputType.number, <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ], RegExp(r'\d+'))),
ListTile(title: _buildInputTile(_mapQueryLimitController, 'Maximum other points', 'MAP.PROX.MAX_PER_QUERY', TextInputType.number, <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ], RegExp(r'\d+'), enabled: _advancedMapOptions)),
ListTile(title: _buildInputTile(_mapQueryAgeController, 'Maximum age of other points (seconds)', 'MAP.PROX.MAX_AGE_SECONDS', TextInputType.number, <TextInputFormatter>[ FilteringTextInputFormatter.allow('0-9') ], RegExp(r'\d+'), enabled: _advancedMapOptions)),
],
),
],
......@@ -167,7 +171,7 @@ class _SettingsFormState extends State<SettingsForm> {
/// Action buttons (Confirm, Reset)
Row(
children: [
Spacer(flex: 1,),
Spacer(flex: 1),
Expanded(
flex: 10,
child: RaisedButton(
......@@ -180,10 +184,12 @@ class _SettingsFormState extends State<SettingsForm> {
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);
PreferencesHandler().setPreferencesBool(PreferenceKeys.BT__USING_ADVANCED.key, _advancedBTOptions);
PreferencesHandler().setPreferencesString(PreferenceKeys.BT__SERVICE_UUID.key, _bleServiceController.text);
PreferencesHandler().setPreferencesString(PreferenceKeys.BT__CHARACTERISTIC_UUID.key, _bleCharacteristicController.text);
PreferencesHandler().setPreferencesBool(PreferenceKeys.MAP__USING_ADVANCED.key, _advancedMapOptions);
PreferencesHandler().setPreferencesInt(PreferenceKeys.MAP__PROX__MAX_PER_QUERY.key, int.parse(_mapQueryLimitController.text));
PreferencesHandler().setPreferencesInt(PreferenceKeys.MAP__PROX__MAX_AGE_SECONDS.key, int.parse(_mapQueryAgeController.text));
}
},
child: Text(
......@@ -227,7 +233,7 @@ class _SettingsFormState extends State<SettingsForm> {
}
/// This method builds a tile that enables a user to change the value of a setting.
Widget _buildInputTile(TextEditingController controller, String labelText, String key, bool enabled, TextInputType inputType, List<TextInputFormatter> inputFormatters, RegExp validation) {
Widget _buildInputTile(TextEditingController controller, String labelText, String key, TextInputType inputType, List<TextInputFormatter> inputFormatters, RegExp validation, {bool enabled = true}) {
PreferencesHandler().getPreferences(key).then((value) => controller.text = value.toString());
return Container(
child: Row(
......@@ -242,7 +248,7 @@ class _SettingsFormState extends State<SettingsForm> {
contentPadding: EdgeInsets.zero,
),
keyboardType: inputType,
inputFormatters: inputFormatters,
//inputFormatters: inputFormatters,
validator: (value) {
if (value.isEmpty) {
return 'The field cannot be empty';
......
......@@ -46,7 +46,14 @@ class PreferenceKeys {
static const MAP__PROX__MAX_PER_QUERY
= const PreferenceKeys._internal('MAP.PROX.MAX_PER_QUERY', int, 200);
static const MAP__PROX__MAX_AGE_SECONDS
= const PreferenceKeys._internal('MAP.PROX.MAX_AGE_SECONDS', int, 200);
= const PreferenceKeys._internal('MAP.PROX.MAX_AGE_SECONDS', int, 200);
static const NET__MAX_ITEMS_PER_PUSH
= const PreferenceKeys._internal('NET.MAX_ITEMS_PER_PUSH', int, 100);
static const NET__TIME_TO_PUSH
= const PreferenceKeys._internal('NET.TIME_TO_PUSH', int, 60);
static const NET__USE_MOBILE_NET
= const PreferenceKeys._internal('NET.USE_MOBILE_NET', bool, true);
/// Returns a list of all different [PreferenceKeys] objects.
static List<PreferenceKeys> get list => [
......@@ -57,8 +64,11 @@ class PreferenceKeys {
MAP__USING_ADVANCED,
MAP__PROX__MAX_PER_QUERY,
MAP__PROX__MAX_AGE_SECONDS,
NET__MAX_ITEMS_PER_PUSH,
NET__TIME_TO_PUSH,
NET__USE_MOBILE_NET
];
/// This checks that all [PreferenceKeys] have the correct [_type] associated to [_defaultValue].
static bool runAssertion() => !list.map((e) => e.defaultValue.runtimeType == e.type).contains(false);
static bool assertTypesMatch() => !list.map((e) => e.defaultValue.runtimeType == e.type).contains(false);