diff --git a/Mobile_app/smart_mask/.flutter-plugins-dependencies b/Mobile_app/smart_mask/.flutter-plugins-dependencies index e87afaf..533959b 100644 --- a/Mobile_app/smart_mask/.flutter-plugins-dependencies +++ b/Mobile_app/smart_mask/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_blue","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"android":[{"name":"flutter_blue","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_macos-2.0.0\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_linux-2.0.0\\\\","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_windows-2.0.0\\\\","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_blue","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2021-03-24 17:32:08.890493","version":"2.0.3"} \ 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:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"android":[{"name":"flutter_blue","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_macos-2.0.0\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_linux-2.0.0\\\\","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_windows-2.0.0\\\\","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_blue","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2021-03-29 17:49:15.960154","version":"2.0.3"} \ No newline at end of file diff --git a/Mobile_app/smart_mask/lib/src/app.dart b/Mobile_app/smart_mask/lib/src/app.dart index e0abef9..9ee2b5e 100644 --- a/Mobile_app/smart_mask/lib/src/app.dart +++ b/Mobile_app/smart_mask/lib/src/app.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_blue/flutter_blue.dart'; +import 'package:smart_mask/src/logic/blocs/analytics/analytics_bloc.dart'; +import 'package:smart_mask/src/logic/blocs/analytics/analytics_provider.dart'; import 'package:smart_mask/src/logic/blocs/bluetooth/bluetooth_bloc.dart'; import 'package:smart_mask/src/logic/blocs/bluetooth/bluetooth_provider.dart'; import 'package:smart_mask/src/logic/blocs/sensor_data/sensor_data_bloc.dart'; import 'package:smart_mask/src/logic/blocs/sensor_data/sensor_data_provider.dart'; import 'package:smart_mask/src/ui/screens/bluetooth/ble_find_device_screen.dart'; import 'package:smart_mask/src/ui/screens/graphs_screen.dart'; +import 'package:smart_mask/src/ui/screens/analytics_screen.dart'; import 'package:smart_mask/src/ui/screens/sensor_details_screen.dart'; import 'package:smart_mask/src/ui/screens/home_screen.dart'; @@ -22,15 +25,19 @@ class MyApp extends StatelessWidget { final bluetoothBloc = BluetoothBloc(); final sensorDataBloc = SensorDataBloc(); + final analyticsBloc = AnalyticsBloc(); return SensorDataProvider( bloc: sensorDataBloc, child: BluetoothProvider( bloc: bluetoothBloc, - child: MaterialApp( - title: "Smart Mask", - theme: getTheme(), - home: HomeScreen(), + child: AnalyticsProvider( + bloc: analyticsBloc, + child: MaterialApp( + title: "Smart Mask", + theme: getTheme(), + home: SplashScreen(), + ), ), ), ); @@ -51,7 +58,7 @@ class MyApp extends StatelessWidget { } } -class HomeScreen extends StatelessWidget { +class SplashScreen extends StatelessWidget { @override Widget build(BuildContext context) { return TabControl(); @@ -72,7 +79,7 @@ class _TabControlState extends State { bluetoothBloc = BluetoothProvider.of(context); return DefaultTabController( length: choices.length, - initialIndex: 1, + initialIndex: 3, child: Scaffold( appBar: AppBar( title: Row( @@ -144,40 +151,21 @@ List choices = [ Choice( title: 'Home', icon: Icons.home, - widget: (BuildContext context) => Home(), + widget: (BuildContext context) => HomeScreen(), ), Choice( title: 'Graphs', icon: Icons.show_chart, - widget: (BuildContext context) => Graph(), + widget: (BuildContext context) => GraphsScreen(), ), Choice( title: 'Details', icon: Icons.zoom_in, - widget: (BuildContext context) => GraphDetails(), + widget: (BuildContext context) => GraphDetailsScreen(), + ), + Choice( + title: 'Analytics', + icon: Icons.analytics_outlined, + widget: (BuildContext context) => AnalyticsScreen(), ), ]; - -class ChoiceCard extends StatelessWidget { - const ChoiceCard({Key key, this.choice}) : super(key: key); - - final Choice choice; - - @override - Widget build(BuildContext context) { - final TextStyle textStyle = Theme.of(context).textTheme.bodyText2; - return Card( - color: Colors.white, - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon(choice.icon, size: 128.0, color: textStyle.color), - Text(choice.title, style: textStyle), - ], - ), - ), - ); - } -} diff --git a/Mobile_app/smart_mask/lib/src/logic/blocs/analytics/analytics_bloc.dart b/Mobile_app/smart_mask/lib/src/logic/blocs/analytics/analytics_bloc.dart new file mode 100644 index 0000000..ffce9b0 --- /dev/null +++ b/Mobile_app/smart_mask/lib/src/logic/blocs/analytics/analytics_bloc.dart @@ -0,0 +1,173 @@ +// Sensor Data Business Logic (BLoc) provider +// +// Description: +// + +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:smart_mask/src/logic/database/models/sensor_model.dart'; +import 'package:smart_mask/src/logic/repositories/sensor_data_repo.dart'; + +class AnalyticsBloc { + final _sensorDataRepo = SensorDataRepository(); + Sensor _selectedSensor = Sensor.sensor_1; + AnalyticsState _analyticsState = AnalyticsState(); + + BehaviorSubject _selectedSensorSubject; + BehaviorSubject _timeRangeSubject; + BehaviorSubject> _sensorDataProcessedSubject; + BehaviorSubject _analyticsRefresh; + + AnalyticsBloc() { + _selectedSensorSubject = BehaviorSubject(); + _timeRangeSubject = BehaviorSubject(); + _sensorDataProcessedSubject = BehaviorSubject>(); + _analyticsRefresh = BehaviorSubject(); + _analyticsRefresh.stream.listen((event) => processAnalytics()); + setSelectedSensor(_selectedSensor); + } + + processAnalytics() { + print(_analyticsState.toString()); + } + + Future getAvailableInterval() async { + var start = await _sensorDataRepo.getEarliestSensorData(_selectedSensor); + var end = await _sensorDataRepo.getLatestSensorData(_selectedSensor); + var startDate = DateTime.fromMillisecondsSinceEpoch(start.timeStamp); + var endDate = DateTime.fromMillisecondsSinceEpoch(end.timeStamp); + return TimeInterval(startDate, endDate); + } + + getSensorData(TimeInterval interval) async { + List sensorData = await _sensorDataRepo.getSensorData( + _selectedSensor, + interval: [interval.start, interval.end], + ); + _sensorDataProcessedSubject.sink.add(sensorData); + } + + Stream> getSensorDataStream() { + return _sensorDataProcessedSubject.stream; + } + + Stream get timeRangeStream => _timeRangeSubject.stream; + + set timeRange(TimeInterval interval) { + _timeRangeSubject.add(interval); + getSensorData(interval); + } + + void setSelectedSensor(Sensor sensor) { + _selectedSensorSubject.add(sensor); + } + + Stream getSelectedSensorStream() { + return _selectedSensorSubject.stream; + } + + Stream getAnalyticsRefreshStream() { + return _analyticsRefresh.stream; + } + + triggerAnalyticsRefresh() { + _analyticsRefresh.add(true); + } + + setLowPassFilter(double value) { + _analyticsState.lowPassFilter = value; + triggerAnalyticsRefresh(); + } + + setHighPassFilter(double value) { + _analyticsState.highPassFilter = value; + triggerAnalyticsRefresh(); + } + + setTime(int value) { + _analyticsState.time = value; + triggerAnalyticsRefresh(); + } + + increaseZoomLevel() { + _analyticsState.zoomLevel += 1; + triggerAnalyticsRefresh(); + } + + decreaseZoomLevel() { + _analyticsState.zoomLevel -= 1; + triggerAnalyticsRefresh(); + } + + dispose() { + _sensorDataProcessedSubject.close(); + _selectedSensorSubject.close(); + _timeRangeSubject.close(); + } +} + +class TimeInterval { + DateTime start; + DateTime end; + + TimeInterval(this.start, this.end); + + factory TimeInterval.fromMsSinceEpoch(RangeValues range) { + DateTime dateStart = + DateTime.fromMicrosecondsSinceEpoch(range.start.toInt()); + DateTime dateEnd = DateTime.fromMicrosecondsSinceEpoch(range.end.toInt()); + return TimeInterval(dateStart, dateEnd); + } +} + +class AnalyticsState { + int _time; + int _zoomLevel; + double _lowPassFilter; + double _highPassFilter; + + AnalyticsState() { + _time = DateTime.now().millisecondsSinceEpoch; + _zoomLevel = 0; + _lowPassFilter = 100.0; + _highPassFilter = 0.2; + } + + double get lowPassFilter => _lowPassFilter; + + set lowPassFilter(double value) { + if (value < 0 || value <= _highPassFilter || value > 10000) return; + _lowPassFilter = value; + } + + double get highPassFilter => _highPassFilter; + + set highPassFilter(double value) { + if (value < 0 || value >= _lowPassFilter || value > 10000) return; + _highPassFilter = value; + } + + int get time => _time; + + set time(int value) { + int nowMs = DateTime.now().millisecondsSinceEpoch; + int nowMinus1monthMs = + DateTime.now().subtract(Duration(days: 30)).millisecondsSinceEpoch; + if (value > nowMs || value < nowMinus1monthMs) return; + _time = value; + } + + int get zoomLevel => _zoomLevel; + + set zoomLevel(int value) { + if (value < 0 || value > 15) return; + _zoomLevel = value; + } + + @override + String toString() { + return "Analytics State ${DateTime.fromMillisecondsSinceEpoch(_time)}, " + "Zoom level : $_zoomLevel, Low pass $_lowPassFilter, High pass $_highPassFilter"; + } +} diff --git a/Mobile_app/smart_mask/lib/src/logic/blocs/analytics/analytics_provider.dart b/Mobile_app/smart_mask/lib/src/logic/blocs/analytics/analytics_provider.dart new file mode 100644 index 0000000..2293dfc --- /dev/null +++ b/Mobile_app/smart_mask/lib/src/logic/blocs/analytics/analytics_provider.dart @@ -0,0 +1,21 @@ +// Sensor Data Business Logic (BLoc) provider +// +// Description: +// Enable the Sensor Data bloc to be accessible (provided) +// throughout the app with contex.inherit.. + +import 'package:flutter/material.dart'; +import 'package:smart_mask/src/logic/blocs/analytics/analytics_bloc.dart'; + +class AnalyticsProvider extends InheritedWidget { + final AnalyticsBloc bloc; + + AnalyticsProvider({Key key, Widget child, this.bloc}) + : super(key: key, child: child); + + bool updateShouldNotify(_) => true; + + static AnalyticsBloc of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType().bloc; + } +} diff --git a/Mobile_app/smart_mask/lib/src/logic/blocs/sensor_data/sensor_data_bloc.dart b/Mobile_app/smart_mask/lib/src/logic/blocs/sensor_data/sensor_data_bloc.dart index 2d91acb..89c4abb 100644 --- a/Mobile_app/smart_mask/lib/src/logic/blocs/sensor_data/sensor_data_bloc.dart +++ b/Mobile_app/smart_mask/lib/src/logic/blocs/sensor_data/sensor_data_bloc.dart @@ -8,14 +8,16 @@ import 'dart:async'; import 'package:rxdart/rxdart.dart'; +import 'package:smart_mask/src/logic/blocs/sensor_data/sensor_mock.dart'; import 'package:smart_mask/src/logic/database/models/sensor_model.dart'; import 'package:smart_mask/src/logic/repositories/sensor_data_repo.dart'; class SensorDataBloc { final _sensorDataRepo = SensorDataRepository(); Sensor _selectedSensor = Sensor.sensor_1; - Duration windowInterval = Duration(seconds: 30); + Duration windowInterval = Duration(seconds: 10); Duration refreshInterval = Duration(seconds: 1); + SensorsMock sensorsMock; BehaviorSubject _selectedSensorSubject; Map>> _sensorDataSubjects = Map(); @@ -34,6 +36,8 @@ class SensorDataBloc { setSelectedSensor(_selectedSensor); setupTimers(refreshInterval); + + // sensorsMock = SensorsMock(); } getSensorData(Sensor sensor, {List interval}) async { diff --git a/Mobile_app/smart_mask/lib/src/logic/blocs/sensor_data/sensor_mock.dart b/Mobile_app/smart_mask/lib/src/logic/blocs/sensor_data/sensor_mock.dart new file mode 100644 index 0000000..27bc12d --- /dev/null +++ b/Mobile_app/smart_mask/lib/src/logic/blocs/sensor_data/sensor_mock.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:smart_mask/src/logic/database/models/sensor_model.dart'; +import 'dart:math'; + +import 'package:smart_mask/src/logic/repositories/sensor_data_repo.dart'; + +class SensorsMock { + final _sensorDataRepo = SensorDataRepository(); + Map sensorMockData = Map(); + Random rng; + + Duration addInterval = Duration(milliseconds: 200); + Timer mockTimer; + + SensorsMock() { + rng = Random(); + for (var s in Sensor.values) { + sensorMockData[s] = 0; + } + mockTimer = Timer.periodic(addInterval, (Timer t) => addMockData()); + } + + addMockData() { + for (var s in Sensor.values) { + var rand = rng.nextInt(10); + + if (rng.nextBool()) + sensorMockData[s] += rand; + else + sensorMockData[s] -= rand; + + if (sensorMockData[s] < -3000) sensorMockData[s] = -3000; + if (sensorMockData[s] > 3000) sensorMockData[s] = 3000; + + var sensorData = SensorData.fromSensorAndValue( + s, sensorMockData[s], DateTime.now().millisecondsSinceEpoch); + + _sensorDataRepo.insertSensorData(sensorData); + } + } + + dispose() { + mockTimer.cancel(); + } +} diff --git a/Mobile_app/smart_mask/lib/src/logic/database/access_operations/sensor_data_access.dart b/Mobile_app/smart_mask/lib/src/logic/database/access_operations/sensor_data_access.dart index 158a198..99f8704 100644 --- a/Mobile_app/smart_mask/lib/src/logic/database/access_operations/sensor_data_access.dart +++ b/Mobile_app/smart_mask/lib/src/logic/database/access_operations/sensor_data_access.dart @@ -17,6 +17,19 @@ class SensorDataAccess { return result; } + Future getEarliestSensorData(Sensor sensor) async { + final db = await dbProvider.database; + List> result; + String query = + 'SELECT * FROM $sensorDataTABLE WHERE sensor = \'${sensorEnumToString(sensor)}\' ORDER BY ID ASC LIMIT 1'; + + result = await db.rawQuery(query); + + List sensorData = + result.map((item) => SensorData.fromDatabaseJson(item)).toList(); + return sensorData[0]; + } + Future getLatestSensorData(Sensor sensor) async { final db = await dbProvider.database; List> result; diff --git a/Mobile_app/smart_mask/lib/src/logic/repositories/sensor_data_repo.dart b/Mobile_app/smart_mask/lib/src/logic/repositories/sensor_data_repo.dart index a11aca7..071fb5e 100644 --- a/Mobile_app/smart_mask/lib/src/logic/repositories/sensor_data_repo.dart +++ b/Mobile_app/smart_mask/lib/src/logic/repositories/sensor_data_repo.dart @@ -12,17 +12,21 @@ import 'package:smart_mask/src/logic/database/access_operations/sensor_data_acce class SensorDataRepository { final sensorDataAccess = SensorDataAccess(); - Future insertSensorData(SensorData sensorData) => + Future insertSensorData(SensorData sensorData) => sensorDataAccess.createSensorData(sensorData); - Future deleteAllSensorData() => sensorDataAccess.deleteAllSensorData(); + Future deleteAllSensorData() => sensorDataAccess.deleteAllSensorData(); - Future getSensorData(Sensor sensor, {List interval}) => + Future> getSensorData(Sensor sensor, + {List interval}) => sensorDataAccess.getSensorData( sensor, interval: interval, ); - Future getLatestSensorData(Sensor sensor) => + Future getEarliestSensorData(Sensor sensor) => + sensorDataAccess.getEarliestSensorData(sensor); + + Future getLatestSensorData(Sensor sensor) => sensorDataAccess.getLatestSensorData(sensor); } diff --git a/Mobile_app/smart_mask/lib/src/ui/screens/analytics_screen.dart b/Mobile_app/smart_mask/lib/src/ui/screens/analytics_screen.dart new file mode 100644 index 0000000..2ce93e6 --- /dev/null +++ b/Mobile_app/smart_mask/lib/src/ui/screens/analytics_screen.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:smart_mask/src/ui/widgets/analytics_widget.dart'; +import 'package:smart_mask/src/ui/widgets/graph/sensor_graph.dart'; +import 'package:smart_mask/src/logic/database/models/sensor_model.dart'; +import 'package:smart_mask/src/ui/widgets/sensor_control_widgets.dart'; + +const num graphsHeight = 300.0; + +class AnalyticsScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Container( + child: SingleChildScrollView( + child: Column( + children: [ + SensorSelectAnalyticsDropButton(), + SizedBox( + height: graphsHeight, + child: Text("Soon, there will be a graph here"), + // child: SensorGraph( + // sensorDataStream: analyticsBloc.getSensorDataStream(), + // sensor: snapshot.data, + // height: graphsHeight / (Sensor.values.length * 2), + // ), + ), + IntervalSlider(), + ], + ), + ), + ), + ); + } +} + +// class AnalyticsView extends StatelessWidget { +// @override +// Widget build(BuildContext context) { +// AnalyticsBloc analyticsBloc = AnalyticsProvider.of(context); +// return StreamBuilder( +// stream: analyticsBloc.getSelectedSensorStream(), +// builder: (BuildContext context, AsyncSnapshot snapshot) { +// if (snapshot.hasError) return Text('Error'); +// switch (snapshot.connectionState) { +// case ConnectionState.active: +// return SingleChildScrollView( +// child: Column( +// children: [ +// SensorSelectAnalyticsDropButton( +// sensor: snapshot.data, +// changeSensorFunction: analyticsBloc.setSelectedSensor, +// ), +// SizedBox( +// height: graphsHeight, +// // child: Text("Soon, there will be a graph here"), +// child: SensorGraph( +// sensorDataStream: analyticsBloc.getSensorDataStream(), +// sensor: snapshot.data, +// height: graphsHeight / (Sensor.values.length * 2), +// ), +// ), +// IntervalSlider(), +// ], +// )); +// } +// return Text('Data not ready'); +// }, +// ); +// } +// } diff --git a/Mobile_app/smart_mask/lib/src/ui/screens/graphs_screen.dart b/Mobile_app/smart_mask/lib/src/ui/screens/graphs_screen.dart index 0217f4c..1f715f9 100644 --- a/Mobile_app/smart_mask/lib/src/ui/screens/graphs_screen.dart +++ b/Mobile_app/smart_mask/lib/src/ui/screens/graphs_screen.dart @@ -15,58 +15,41 @@ import 'package:smart_mask/src/logic/database/models/sensor_model.dart'; const num graphsHeight = 800.0; -class Graph extends StatelessWidget { +class GraphsScreen extends StatelessWidget { @override Widget build(BuildContext context) { + SensorDataBloc sensorDataBloc = SensorDataProvider.of(context); return SingleChildScrollView( child: Column( children: [ SizedBox( height: graphsHeight, - child: RefreshingGraphs(), + child: ListView.builder( + itemCount: Sensor.values.length, + itemBuilder: (context, index) { + Sensor sensor = Sensor.values[index]; + return ListTile( + title: Row(children: [ + Text(sensorEnumToString(sensor).toUpperCase()), + Expanded(child: Container()), + ElevatedButton( + onPressed: () { + sensorDataBloc.setSelectedSensor(sensor); + DefaultTabController.of(context).animateTo(2); + }, + child: Text("Details"), + ), + ]), + subtitle: SensorGraph( + sensorDataStream: sensorDataBloc.getStream(sensor), + sensor: sensor, + height: graphsHeight / (Sensor.values.length * 2), + ), + ); + }, + ), ), ], )); } } - -class RefreshingGraphs extends StatefulWidget { - @override - _RefreshingGraphsState createState() => _RefreshingGraphsState(); -} - -class _RefreshingGraphsState extends State { - SensorDataBloc sensorDataBloc; - - @override - Widget build(BuildContext context) { - sensorDataBloc = SensorDataProvider.of(context); - - return ListView.builder( - itemCount: Sensor.values.length, - itemBuilder: (context, index) { - Sensor sensor = Sensor.values[index]; - - return ListTile( - title: Row(children: [ - Text(sensorEnumToString(sensor).toUpperCase()), - Expanded(child: Container()), - ElevatedButton( - onPressed: () => navigateSensorDetails(sensor), - child: Text("Details")) - ]), - subtitle: SensorGraph( - sensorDataStream: sensorDataBloc.getStream(sensor), - sensor: sensor, - height: graphsHeight / (Sensor.values.length * 2), - ), - ); - }, - ); - } - - void navigateSensorDetails(Sensor sensor) { - sensorDataBloc.setSelectedSensor(sensor); - DefaultTabController.of(context).animateTo(2); - } -} diff --git a/Mobile_app/smart_mask/lib/src/ui/screens/home_screen.dart b/Mobile_app/smart_mask/lib/src/ui/screens/home_screen.dart index 5aaf320..c43278e 100644 --- a/Mobile_app/smart_mask/lib/src/ui/screens/home_screen.dart +++ b/Mobile_app/smart_mask/lib/src/ui/screens/home_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -class Home extends StatelessWidget { +class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { print("location need to be enabled for bluetooth discovery"); diff --git a/Mobile_app/smart_mask/lib/src/ui/screens/sensor_details_screen.dart b/Mobile_app/smart_mask/lib/src/ui/screens/sensor_details_screen.dart index 7d1b5dc..ca86eba 100644 --- a/Mobile_app/smart_mask/lib/src/ui/screens/sensor_details_screen.dart +++ b/Mobile_app/smart_mask/lib/src/ui/screens/sensor_details_screen.dart @@ -15,26 +15,11 @@ import 'package:smart_mask/src/ui/widgets/sensor_control_widgets.dart'; const num graphsHeight = 300.0; -class GraphDetails extends StatelessWidget { +class GraphDetailsScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return RefreshingGraph(); - } -} - -class RefreshingGraph extends StatefulWidget { - @override - _RefreshingGraphState createState() => _RefreshingGraphState(); -} - -class _RefreshingGraphState extends State { - SensorDataBloc sensorDataBloc; - BluetoothBloc bluetoothBloc; - - @override - Widget build(BuildContext context) { - sensorDataBloc = SensorDataProvider.of(context); - bluetoothBloc = BluetoothProvider.of(context); + SensorDataBloc sensorDataBloc = SensorDataProvider.of(context); + BluetoothBloc bluetoothBloc = BluetoothProvider.of(context); return StreamBuilder( stream: sensorDataBloc.getSelectedSensorStream(), @@ -51,7 +36,7 @@ class _RefreshingGraphState extends State { return SingleChildScrollView( child: Column( children: [ - DropButton( + SensorSelectDropButton( sensor: snapshot.data, changeSensorFunction: sensorDataBloc.setSelectedSensor, ), diff --git a/Mobile_app/smart_mask/lib/src/ui/widgets/analytics_widget.dart b/Mobile_app/smart_mask/lib/src/ui/widgets/analytics_widget.dart new file mode 100644 index 0000000..44c0bc2 --- /dev/null +++ b/Mobile_app/smart_mask/lib/src/ui/widgets/analytics_widget.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:smart_mask/src/logic/database/models/sensor_model.dart'; + +import 'package:smart_mask/src/logic/blocs/analytics/analytics_bloc.dart'; +import 'package:smart_mask/src/logic/blocs/analytics/analytics_provider.dart'; + +/////////////////////////////////////////////////////////////////////////////// + +class SensorSelectAnalyticsDropButton extends StatelessWidget { + const SensorSelectAnalyticsDropButton({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + AnalyticsBloc sensorDataBloc = AnalyticsProvider.of(context); + final List sensors = + Sensor.values.map((Sensor s) => sensorEnumToString(s)).toList(); + + return StreamBuilder( + stream: sensorDataBloc.getSelectedSensorStream(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasError) return Text("error"); + switch (snapshot.connectionState) { + case ConnectionState.none: + return Text("ConnectionNone"); + case ConnectionState.waiting: + return Text("ConnectionWaiting"); + case ConnectionState.done: + return Text("ConnectionDone"); + case ConnectionState.active: + return DropdownButton( + value: sensorEnumToString(snapshot.data), + icon: Icon(Icons.arrow_downward), + iconSize: 24, + elevation: 16, + onChanged: (String newSensor) { + Sensor sensor = sensorStringToEnum(newSensor); + sensorDataBloc.setSelectedSensor(sensor); + }, + items: sensors.map>( + (String value) { + return DropdownMenuItem( + value: value, + child: Text(value.toUpperCase()), + ); + }, + ).toList(), + ); + } + return null; // unreachable + }, + ); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +class IntervalSlider extends StatefulWidget { + const IntervalSlider({Key key}) : super(key: key); + + @override + _IntervalSliderState createState() => _IntervalSliderState(); +} + +class _IntervalSliderState extends State { + double _currentSliderValue; + double _zoomLevel; + AnalyticsBloc sensorDataBloc; + + // @override + // void didUpdateWidget(dynamic oldWidget) { + // if (_currentSliderValue != widget.initialValue) { + // setState(() { + // _currentSliderValue = widget.initialValue; + // }); + // } + // super.didUpdateWidget(oldWidget); + // } + + @override + void initState() { + super.initState(); + _currentSliderValue = 0; + _zoomLevel = 0; + } + + @override + Widget build(BuildContext context) { + sensorDataBloc = AnalyticsProvider.of(context); + return Card( + margin: EdgeInsets.all(10), + child: Row( + children: [ + Expanded( + flex: 8, + child: Column( + children: [ + Container(child: Text("Navigate Your Data")), + Container( + padding: EdgeInsets.only(top: 10), + child: timeSlider(), + ), + ], + ), + ), + Expanded( + flex: 2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + zoomInButton(), + zoomOutButton(), + ], + ), + ), + ], + ), + ); + } + + Widget timeSlider() { + return Slider( + value: _currentSliderValue, + min: 0, + max: 1000, + divisions: 99, + onChangeEnd: (double value) { + sensorDataBloc.setTime(value.toInt()); + }, + onChanged: (double x) { + setState(() => _currentSliderValue = x); + }, + ); + } + + Widget zoomInButton() { + return ElevatedButton( + onPressed: () => sensorDataBloc.increaseZoomLevel(), + child: Text("+"), + ); + } + + Widget zoomOutButton() { + return ElevatedButton( + onPressed: () => sensorDataBloc.decreaseZoomLevel(), + child: Text("-"), + ); + } +} + +/////////////////////////////////////////////////////////////////////////////// diff --git a/Mobile_app/smart_mask/lib/src/ui/widgets/graph/analytics_widgets.dart b/Mobile_app/smart_mask/lib/src/ui/widgets/graph/analytics_widgets.dart new file mode 100644 index 0000000..e69de29 diff --git a/Mobile_app/smart_mask/lib/src/ui/widgets/sensor_control_widgets.dart b/Mobile_app/smart_mask/lib/src/ui/widgets/sensor_control_widgets.dart index 0a63c07..07fe3e4 100644 --- a/Mobile_app/smart_mask/lib/src/ui/widgets/sensor_control_widgets.dart +++ b/Mobile_app/smart_mask/lib/src/ui/widgets/sensor_control_widgets.dart @@ -7,11 +7,11 @@ import 'package:flutter/material.dart'; import 'package:smart_mask/src/logic/database/models/sensor_model.dart'; import 'package:smart_mask/src/logic/database/models/sensor_control_model.dart'; -class DropButton extends StatelessWidget { +class SensorSelectDropButton extends StatelessWidget { final Sensor sensor; final void Function(Sensor) changeSensorFunction; - const DropButton({Key key, this.sensor, this.changeSensorFunction}) + const SensorSelectDropButton({Key key, this.sensor, this.changeSensorFunction}) : super(key: key); @override