diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 515ad969..98c8e964 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -44,4 +44,8 @@ android:name="flutterEmbedding" android:value="2" /> + + + + diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index bf09ebe1..e048b141 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; - import 'src/app.dart'; -void main() async { +void main() { runApp(App()); } diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart index be3bfef4..f8056bce 100644 --- a/mobile/lib/src/app.dart +++ b/mobile/lib/src/app.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/presentation/bluetooth_route/bluetoothSelector_route.dart'; +import 'package:polydodo/src/presentation/wallets/wallets_route.dart'; -import 'presentation/wallets/wallets_route.dart'; import 'locator.dart'; import 'theme.dart'; @@ -18,10 +19,10 @@ class App extends StatelessWidget { child: MaterialApp( title: 'PolyDodo', theme: theme, - home: WalletsRoute(), - initialRoute: WalletsRoute.name, + initialRoute: BluetoothSelectorRoute.name, routes: { WalletsRoute.name: (context) => WalletsRoute(), + BluetoothSelectorRoute.name: (context) => BluetoothSelectorRoute(), }, ), ); diff --git a/mobile/lib/src/application/device/device_selector_cubit.dart b/mobile/lib/src/application/device/device_selector_cubit.dart new file mode 100644 index 00000000..6d876880 --- /dev/null +++ b/mobile/lib/src/application/device/device_selector_cubit.dart @@ -0,0 +1,45 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/domain/acquisition_device/acquisition_device.dart'; +import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; +import 'device_selector_state.dart'; + +class DeviceSelectorCubit extends Cubit { + final IAcquisitionDeviceRepository _deviceRepository; + + StreamSubscription> _acquisitionDeviceStream; + + DeviceSelectorCubit(this._deviceRepository) : super(DeviceInitial()) { + startSearching(); + } + + void startSearching() { + _deviceRepository.initializeRepository(); + + if (_acquisitionDeviceStream == null) { + _acquisitionDeviceStream = _deviceRepository + .watch() + .asBroadcastStream() + .listen((devices) => emit(DeviceSearchInProgress(devices)), + onError: (e) => emit(DeviceSearchFailure(e))); + } + } + + void connect(AcquisitionDevice device) async { + emit(DeviceConnectionInProgress()); + + _deviceRepository.connect(device).then( + (value) => { + _acquisitionDeviceStream.cancel(), + emit(DeviceConnectionSuccess()) + }, + onError: (e) => {emit(DeviceConnectionFailure(e)), resetSearch()}); + } + + void resetSearch() { + _deviceRepository.disconnect(); + startSearching(); + } +} diff --git a/mobile/lib/src/application/device/device_selector_state.dart b/mobile/lib/src/application/device/device_selector_state.dart new file mode 100644 index 00000000..76677bb6 --- /dev/null +++ b/mobile/lib/src/application/device/device_selector_state.dart @@ -0,0 +1,27 @@ +import 'package:polydodo/src/domain/acquisition_device/acquisition_device.dart'; + +abstract class DeviceState {} + +class DeviceInitial extends DeviceState {} + +class DeviceSearchInProgress extends DeviceState { + final List devices; + + DeviceSearchInProgress(this.devices); +} + +class DeviceSearchFailure extends DeviceState { + final Exception cause; + + DeviceSearchFailure(this.cause); +} + +class DeviceConnectionInProgress extends DeviceState {} + +class DeviceConnectionSuccess extends DeviceState {} + +class DeviceConnectionFailure extends DeviceState { + final Exception cause; + + DeviceConnectionFailure(this.cause); +} diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart new file mode 100644 index 00000000..5f06fa8f --- /dev/null +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -0,0 +1,27 @@ +import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; + +import 'data_states.dart'; +import 'package:bloc/bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DataCubit extends Cubit { + final IAcquisitionDeviceRepository _deviceRepository; + final IEEGDataRepository _eegDataRepository; + + DataCubit(this._deviceRepository, this._eegDataRepository) + : super(DataStateInitial()); + + void startStreaming() { + emit(DataStateRecording()); + _deviceRepository + .startDataStream() + .then((stream) => _eegDataRepository.createRecordingFromStream(stream)); + } + + void stopStreaming() { + emit(DataStateInitial()); + _deviceRepository.stopDataStream(); + _eegDataRepository.stopRecordingFromStream(); + } +} diff --git a/mobile/lib/src/application/eeg_data/data_states.dart b/mobile/lib/src/application/eeg_data/data_states.dart new file mode 100644 index 00000000..69453e90 --- /dev/null +++ b/mobile/lib/src/application/eeg_data/data_states.dart @@ -0,0 +1,5 @@ +abstract class DataState {} + +class DataStateInitial extends DataState {} + +class DataStateRecording extends DataState {} diff --git a/mobile/lib/src/domain/acquisition_device/acquisition_device.dart b/mobile/lib/src/domain/acquisition_device/acquisition_device.dart new file mode 100644 index 00000000..f15efc14 --- /dev/null +++ b/mobile/lib/src/domain/acquisition_device/acquisition_device.dart @@ -0,0 +1,13 @@ +import 'package:equatable/equatable.dart'; +import 'package:polydodo/src/domain/entity.dart'; + +import '../unique_id.dart'; + +class AcquisitionDevice extends Entity { + final String name; + + AcquisitionDevice( + id, + this.name, + ) : super(id); +} diff --git a/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart new file mode 100644 index 00000000..04067b8e --- /dev/null +++ b/mobile/lib/src/domain/acquisition_device/i_acquisition_device_repository.dart @@ -0,0 +1,13 @@ +import 'acquisition_device.dart'; + +abstract class IAcquisitionDeviceRepository { + void initializeRepository(); + + Future connect(AcquisitionDevice device); + void disconnect(); + + Future>> startDataStream(); + void stopDataStream(); + + Stream> watch(); +} diff --git a/mobile/lib/src/domain/eeg_data/eeg_data.dart b/mobile/lib/src/domain/eeg_data/eeg_data.dart new file mode 100644 index 00000000..4f6b38ab --- /dev/null +++ b/mobile/lib/src/domain/eeg_data/eeg_data.dart @@ -0,0 +1,16 @@ +// EEGData can be extended later to add our metrics +import '../unique_id.dart'; + +class EEGData { + UniqueId id; + List _values; + int sampleCounter = 0; + + EEGData(this.id, this._values) + : assert(id != null), + assert(_values != null); + + List get values => _values; + + String get fileName => id.toString(); +} diff --git a/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart b/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart new file mode 100644 index 00000000..d1e12b38 --- /dev/null +++ b/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart @@ -0,0 +1,8 @@ +abstract class IEEGDataRepository { + void createRecordingFromStream(Stream> stream); + void stopRecordingFromStream(); + + // todo: implement export and import + void importData(); + void exportData(); +} diff --git a/mobile/lib/src/domain/entity.dart b/mobile/lib/src/domain/entity.dart new file mode 100644 index 00000000..844514b4 --- /dev/null +++ b/mobile/lib/src/domain/entity.dart @@ -0,0 +1,11 @@ +import 'package:equatable/equatable.dart'; +import 'package:polydodo/src/domain/unique_id.dart'; + +abstract class Entity extends Equatable { + final UniqueId id; + + Entity(this.id); + + @override + List get props => [id]; +} diff --git a/mobile/lib/src/domain/unique_id.dart b/mobile/lib/src/domain/unique_id.dart index 9b301f89..56895b7e 100644 --- a/mobile/lib/src/domain/unique_id.dart +++ b/mobile/lib/src/domain/unique_id.dart @@ -16,5 +16,5 @@ class UniqueId extends Equatable { List get props => [_id]; @override - bool get stringify => true; + toString() => _id; } diff --git a/mobile/lib/src/infrastructure/bluetooth_repository.dart b/mobile/lib/src/infrastructure/bluetooth_repository.dart new file mode 100644 index 00000000..cc087ab3 --- /dev/null +++ b/mobile/lib/src/infrastructure/bluetooth_repository.dart @@ -0,0 +1,135 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_blue/flutter_blue.dart'; +import 'package:polydodo/src/domain/acquisition_device/acquisition_device.dart'; +import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; +import 'package:polydodo/src/domain/unique_id.dart'; + +class BluetoothRepository implements IAcquisitionDeviceRepository { + static const String BLE_SERVICE = "fe84"; + static const String BLE_RECEIVE = "2d30c082"; + static const String BLE_SEND = "2d30c083"; + static const startStreamChar = 'b'; + static const stopStreamChar = 's'; + + BluetoothDevice _selectedDevice; + BluetoothCharacteristic _sendCharacteristic; + BluetoothCharacteristic _receiveCharacteristic; + + FlutterBlue flutterBlue; + StreamSubscription> _bluetoothScanSubscription; + List _acquisitionDevicePersistency = []; + List _bluetoothDevices = []; + final streamController = StreamController>(); + + BluetoothRepository(); + + void initializeRepository() { + if (_bluetoothScanSubscription == null) { + flutterBlue = FlutterBlue.instance; + + flutterBlue.connectedDevices + .asStream() + .asBroadcastStream() + .listen((List devices) { + for (BluetoothDevice device in devices) { + addDevice(device); + } + }); + _bluetoothScanSubscription = + flutterBlue.scanResults.listen((List results) { + for (ScanResult result in results) { + addDevice(result.device); + } + }); + } else { + _bluetoothScanSubscription.resume(); + } + flutterBlue.startScan(); + } + + void addDevice(BluetoothDevice bluetoothDevice) { + AcquisitionDevice device = AcquisitionDevice( + UniqueId.from(bluetoothDevice.id.toString()), bluetoothDevice.name); + + final idx = _acquisitionDevicePersistency.indexOf(device); + + if (idx == -1) { + _acquisitionDevicePersistency.add(device); + _bluetoothDevices.add(bluetoothDevice); + } else { + _acquisitionDevicePersistency[idx] = device; + _bluetoothDevices[idx] = bluetoothDevice; + } + + streamController.add(_acquisitionDevicePersistency); + } + + Future connect(AcquisitionDevice device) async { + _selectedDevice = + _bluetoothDevices[_acquisitionDevicePersistency.indexOf(device)]; + + _acquisitionDevicePersistency.clear(); + _bluetoothDevices.clear(); + _bluetoothScanSubscription.pause(); + flutterBlue.stopScan(); + + try { + await _selectedDevice + .connect() + .then((value) => findRelevantCharacteristics()) + .timeout(Duration(seconds: 6), + onTimeout: () => + {disconnect(), throw Exception("Connection Timed out")}); + } catch (e) { + if (e is PlatformException) { + if (e.code != "already_connected") throw Exception(e.details); + } else + throw e; + } + + return; + } + + void disconnect() async { + if (_selectedDevice != null) { + await _selectedDevice.disconnect(); + _selectedDevice = null; + } + } + + void findRelevantCharacteristics() { + _selectedDevice.discoverServices().then((services) => { + for (BluetoothCharacteristic characteristic in (services.where( + (service) => service.uuid.toString().contains(BLE_SERVICE))) + .first + .characteristics) + { + if (characteristic.uuid.toString().contains(BLE_RECEIVE)) + {_receiveCharacteristic = characteristic} + else if (characteristic.uuid.toString().contains(BLE_SEND)) + {_sendCharacteristic = characteristic} + }, + if (_receiveCharacteristic == null) + throw Exception('Device is missing receive Characteristic'), + if (_sendCharacteristic == null) + throw Exception('Device is missing send Characteristic') + }); + } + + Future>> startDataStream() async { + await _receiveCharacteristic.setNotifyValue(true); + + await _sendCharacteristic.write(startStreamChar.codeUnits); + return _receiveCharacteristic.value; + } + + void stopDataStream() async { + await _receiveCharacteristic.setNotifyValue(false); + await _sendCharacteristic.write(stopStreamChar.codeUnits); + } + + @override + Stream> watch() => streamController.stream; +} diff --git a/mobile/lib/src/infrastructure/constants.dart b/mobile/lib/src/infrastructure/constants.dart new file mode 100644 index 00000000..cc7f7932 --- /dev/null +++ b/mobile/lib/src/infrastructure/constants.dart @@ -0,0 +1,23 @@ +const OPEN_BCI_HEADER = [ + ["%OpenBCI Raw EEG Data"], + ["%Number of channels = 4"], + ["%Sample Rate = 200 Hz"], + ["%Board = OpenBCI_GUI\$BoardGanglionBLE"], + [ + "Sample Index", + " EXG Channel 0", + " EXG Channel 1", + " EXG Channel 2", + " EXG Channel 3", + " Accel Channel 0", + " Accel Channel 1", + " Accel Channel 2", + " Other", + " Other", + " Other", + " Other", + " Other", + " Timestamp", + " Timestamp (Formatted)" + ] +]; diff --git a/mobile/lib/src/infrastructure/eeg_data_repository.dart b/mobile/lib/src/infrastructure/eeg_data_repository.dart new file mode 100644 index 00000000..0eabb1bd --- /dev/null +++ b/mobile/lib/src/infrastructure/eeg_data_repository.dart @@ -0,0 +1,158 @@ +import 'dart:io'; + +import 'package:csv/csv.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:polydodo/src/domain/eeg_data/eeg_data.dart'; +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; +import 'package:polydodo/src/domain/unique_id.dart'; +import 'constants.dart'; + +class EEGDataRepository implements IEEGDataRepository { + bool initializeStream = false; + EEGData recordingData; + List lastSampleData = [0, 0, 0, 0, 0]; + int packetLoss; + int totalPackets; + int nextPacketId; + + void createRecordingFromStream(Stream> stream) { + recordingData = + new EEGData(UniqueId.from(DateTime.now().toString()), new List()); + packetLoss = 0; + totalPackets = 0; + if (!initializeStream) { + initializeStream = true; + stream.listen((value) { + addData(value); + }); + } + } + + void stopRecordingFromStream() async { + // todo: move save future to another file + final directory = await getExternalStorageDirectory(); + final pathOfTheFileToWrite = + directory.path + '/' + recordingData.fileName + ".txt"; + File file = File(pathOfTheFileToWrite); + List fileContent = new List(); + fileContent.addAll(OPEN_BCI_HEADER); + fileContent.addAll(recordingData.values); + String csv = const ListToCsvConverter().convert(fileContent); + await file.writeAsString(csv); + } + + // todo: implement export and import + void importData() {} + void exportData() {} + + void addData(List event) async { + //print("Lost packets: " + packetLoss.toString()); + //print("Total packets: " + totalPackets.toString()); + //print("Lost percentage: " + + // (packetLoss.toDouble() / totalPackets.toDouble()).toString()); + if (event.length != 20) { + print("Invalid Event"); + return; + } + totalPackets++; + int packetID = event[0]; + + // todo: handle packet id 0 (raw data) and possibly impedence for signal validation + if (packetID == 0) { + nextPacketId = 101; + + List data = parseRaw(event); + data = convertToMicrovolts(data, false); + + recordingData.values.add(data.sublist(0, 15)); + } else if (packetID >= 101 && packetID <= 200) { + // print(packetID); + // print(nextPacketId); + packetLoss += packetID - nextPacketId; + nextPacketId = packetID + 1; + List data = parse19Bit(event); + data = convertToMicrovolts(data, true); + + recordingData.values.add(data.sublist(0, 15)); + recordingData.values.add(data.sublist(15, 30)); + } + } + + List parseRaw(event) { + List data = getListForCSV(); + + data[0] = recordingData.sampleCounter++; + data[1] = (event[1] << 16) | (event[2] << 8) | event[3]; + data[2] = (event[4] << 16) | (event[5] << 8) | event[6]; + data[3] = (event[7] << 16) | (event[8] << 8) | event[9]; + data[4] = (event[10] << 16) | (event[11] << 8) | event[12]; + + return data; + } + + List parse19Bit(event) { + // Test event, comment scale factor + // event = [ 101, 0, 0, 0, 0, 8, 0, 5, 0, 0, 72, 0, 9, 240, 1, 176, 0, 48, 0, 8]; // Positive Test + // Expected [[0, 2, 10, 4], [262148, 507910, 393222, 8]] + // event = [ 101, 255, 255, 191, 255, 239, 255, 252, 255, 255, 88, 0, 11, 62, 56, 224, 0, 63, 240, 1 ]; // Negative Test + // Expected [[-3, -5, -7, -11], [-262139, -198429, -262137, -4095]] + + List data = getListForCSV(); + + data[0] = recordingData.sampleCounter; + data[1] = (event[1] << 11) | (event[2] << 3) | (event[3] >> 5); + data[2] = ((event[3] & 31) << 14) | (event[4] << 6) | (event[5] >> 2); + data[3] = ((event[5] & 3) << 17) | + (event[6] << 9) | + (event[7] << 1) | + (event[8] >> 7); + data[4] = ((event[8] & 127) << 12) | (event[9] << 4) | (event[10] >> 4); + data[15] = recordingData.sampleCounter++; + data[16] = ((event[10] & 15) << 15) | (event[11] << 7) | (event[12] >> 1); + data[17] = ((event[12] & 1) << 18) | + (event[13] << 10) | + (event[14] << 2) | + (event[15] >> 6); + data[18] = ((event[15] & 63) << 13) | (event[16] << 5) | (event[17] >> 3); + data[19] = ((event[17] & 7) << 16) | (event[18] << 8) | (event[19]); + + return data; + } + + List getListForCSV() { + List data = new List(30); + + for (int i = 5; i < 15; ++i) { + data[i] = 0; + data[i + 15] = 0; + } + return data; + } + + List convertToMicrovolts(List data, bool isDelta) { + for (int i = 1; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + if (j == 1 && !isDelta) break; + + int offset = 15 * j; + String binary = data[i + offset].toRadixString(2); + + // Handle negatives + if (isDelta && binary[binary.length - 1] == '1') { + data[i + offset] = (~data[i + offset] & 524287 | 1) * -1; + } + + // Convert to microvolts using the scale factor + data[i + offset] = + data[i + offset].toDouble() * (1200000 / (8388607.0 * 1.5 * 51.0)); + + // Convert delta + if (isDelta) data[i + offset] = lastSampleData[i] - data[i + offset]; + + lastSampleData[i] = data[i + offset]; + } + } + + return data; + } +} diff --git a/mobile/lib/src/infrastructure/serial_repository.dart b/mobile/lib/src/infrastructure/serial_repository.dart new file mode 100644 index 00000000..e69de29b diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index bac78b6f..560237f3 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -1,18 +1,25 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_blue/flutter_blue.dart'; import 'package:get_it/get_it.dart'; - -import 'package:polydodo/src/domain/wallet/i_wallet_repository.dart'; +import 'package:polydodo/src/application/device/device_selector_cubit.dart'; +import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; +import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; import 'package:polydodo/src/infrastructure/mock_wallet_repository.dart'; import 'application/wallets/wallets_cubit.dart'; +import 'domain/eeg_data/i_eeg_data_repository.dart'; +import 'domain/wallet/i_wallet_repository.dart'; +import 'infrastructure/bluetooth_repository.dart'; +import 'infrastructure/eeg_data_repository.dart'; /// Private GetIt instance as we want all DI to be performed here in this file final _serviceLocator = GetIt.asNewInstance(); void registerServices() { - _serviceLocator.registerSingleton( - MockWalletRepository(), - ); + _serviceLocator.registerSingleton(MockWalletRepository()); + _serviceLocator + .registerSingleton(BluetoothRepository()); + _serviceLocator.registerSingleton(EEGDataRepository()); } /// This function creates all the BlocProviders used in this app @@ -22,4 +29,15 @@ List createBlocProviders() => [ _serviceLocator.get(), ), ), + BlocProvider( + create: (context) => DeviceSelectorCubit( + _serviceLocator.get(), + ), + ), + BlocProvider( + create: (context) => DataCubit( + _serviceLocator.get(), + _serviceLocator.get(), + ), + ), ]; diff --git a/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart new file mode 100644 index 00000000..eaef0c99 --- /dev/null +++ b/mobile/lib/src/presentation/bluetooth_route/bluetoothSelector_route.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/device/device_selector_cubit.dart'; +import 'package:polydodo/src/application/device/device_selector_state.dart'; +import 'package:polydodo/src/presentation/recording/recording_route.dart'; + +class BluetoothSelectorRoute extends StatelessWidget { + static const name = 'bluetoothRoute'; + + BluetoothSelectorRoute({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Polydodo')), + body: BlocConsumer( + listener: (context, state) { + print(state.runtimeType); + if (state is DeviceSearchFailure) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + 'Unable to search for bluetooth devices because ${state.cause}'), + )); + } else if (state is DeviceConnectionFailure) { + Scaffold.of(context).showSnackBar(SnackBar( + content: + Text('Unable to connect to device because ${state.cause}'), + )); + } else if (state is DeviceConnectionSuccess) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RecordingRoute(), + )); + } + }, + builder: (context, state) { + if (state is DeviceSearchInProgress) + return ListView.builder( + itemCount: state.devices.length, + itemBuilder: (context, index) { + return Card( + child: ListTile( + onTap: () => + BlocProvider.of(context) + .connect(state.devices[index]), + title: Text(state.devices[index].name), + subtitle: Text(state.devices[index].id.toString())), + ); + }); + else + return Container(); + }, + ), + ); + } +} diff --git a/mobile/lib/src/presentation/recording/recording_route.dart b/mobile/lib/src/presentation/recording/recording_route.dart new file mode 100644 index 00000000..bcb475ac --- /dev/null +++ b/mobile/lib/src/presentation/recording/recording_route.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; +import 'package:polydodo/src/application/eeg_data/data_states.dart'; + +class RecordingRoute extends StatelessWidget { + static const name = 'recordingRoute'; + + RecordingRoute({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text("Recording"), + ), + body: BlocConsumer( + listener: (context, state) { + print(state.runtimeType); + }, + builder: (context, state) { + if (state is DataStateInitial) + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text("Start"), + onPressed: () => + BlocProvider.of(context).startStreaming(), + ), + ]), + ); + else + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text("Stop"), + onPressed: () => + BlocProvider.of(context).stopStreaming(), + ), + ]), + ); + }, + ), + ); + } +} diff --git a/mobile/lib/src/presentation/wallets/wallets_route.dart b/mobile/lib/src/presentation/wallets/wallets_route.dart index c5e9af1e..842784d5 100644 --- a/mobile/lib/src/presentation/wallets/wallets_route.dart +++ b/mobile/lib/src/presentation/wallets/wallets_route.dart @@ -18,6 +18,7 @@ class WalletsRoute extends StatelessWidget { appBar: AppBar(title: Text('Polydodo')), body: BlocConsumer( listener: (context, state) { + print(state.runtimeType); if (state is WalletsLoadFailure) { Scaffold.of(context).showSnackBar(SnackBar( content: Text('Unable to load Wallets because ${state.cause}'), diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 37bb9e89..920435e6 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -36,6 +36,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + binary: + dependency: "direct main" + description: + name: binary + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" bloc: dependency: "direct main" description: @@ -92,6 +99,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.5" + csv: + dependency: "direct main" + description: + name: csv + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.3" cupertino_icons: dependency: "direct main" description: @@ -113,6 +127,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" fixnum: dependency: transitive description: @@ -131,7 +159,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "6.0.5" + version: "6.0.6" flutter_blue: dependency: "direct main" description: @@ -158,6 +186,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.4+1" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" matcher: dependency: transitive description: @@ -193,6 +228,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.18" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+2" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+4" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+1" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" plugin_platform_interface: dependency: transitive description: @@ -200,6 +277,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.13" protobuf: dependency: transitive description: @@ -303,6 +387,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" sdks: dart: ">=2.9.0-14.0.dev <3.0.0" flutter: ">=1.16.0 <2.0.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 9a058bed..d082d4d9 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -37,8 +37,11 @@ dependencies: hive: ^1.4.4 meta: ^1.1.8 share: ^0.6.5 - usb_serial: ^0.2.4 uuid: ^2.2.2 + usb_serial: ^0.2.4 + binary: ^2.0.0 + csv: ^4.0.3 + path_provider: ^1.6.16 dev_dependencies: flutter_test: diff --git a/polydodo.code-workspace b/polydodo.code-workspace index 7057fb1b..c191fdab 100644 --- a/polydodo.code-workspace +++ b/polydodo.code-workspace @@ -19,7 +19,7 @@ { "name": "Web client", "path": "web" - }, + } ], "settings": { "prettier.configPath": "../.prettierrc.yaml", @@ -36,6 +36,9 @@ "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[dart]": { + "editor.defaultFormatter": "Dart-Code.dart-code" + }, "python.linting.enabled": true, "python.linting.flake8Enabled": true, "python.formatting.provider": "autopep8",