diff --git a/mobile/lib/src/app.dart b/mobile/lib/src/app.dart index f5a40f5f..b53ce123 100644 --- a/mobile/lib/src/app.dart +++ b/mobile/lib/src/app.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'database.dart'; import 'locator.dart'; import 'presentation/navigation/routes/router.gr.dart' as auto_router; @@ -8,6 +9,11 @@ import 'theme.dart'; class App extends StatelessWidget { App() { + init(); + } + + void init() async { + await initDatabase(); registerServices(); } diff --git a/mobile/lib/src/application/sleep_sequence_history/sleep_sequence_history_cubit.dart b/mobile/lib/src/application/sleep_sequence_history/sleep_sequence_history_cubit.dart index e31c5ad8..d8d96d10 100644 --- a/mobile/lib/src/application/sleep_sequence_history/sleep_sequence_history_cubit.dart +++ b/mobile/lib/src/application/sleep_sequence_history/sleep_sequence_history_cubit.dart @@ -8,7 +8,7 @@ import 'package:polydodo/src/domain/sleep_sequence/sleep_sequence_stats.dart'; import 'sleep_sequence_history_state.dart'; class SleepSequenceHistoryCubit extends Cubit { - final ISleepSequenceRepository _sleepHistoryRepository; + final ISleepSequenceRepository _sleepSequenceRepository; final SleepSequenceStatsCubit _sleepSequenceStatsCubit; final StreamController _selectText = StreamController.broadcast(); @@ -16,14 +16,13 @@ class SleepSequenceHistoryCubit extends Cubit { List _selectedSequences; SleepSequenceHistoryCubit( - this._sleepHistoryRepository, this._sleepSequenceStatsCubit) + this._sleepSequenceRepository, this._sleepSequenceStatsCubit) : super(SleepSequenceHistoryInitial()) { loadHistory(); } void loadHistory() { - emit(SleepSequenceHistoryLoaded( - _sleepHistoryRepository.getSleepSequences())); + emit(SleepSequenceHistoryLoaded(_sleepSequenceRepository.getAll())); } void loadSleepSequence(SleepSequenceStats sequence) { @@ -42,14 +41,13 @@ class SleepSequenceHistoryCubit extends Cubit { _selectedSequences = []; _selectText.add('Done'); emit(SleepSequenceHistoryEditInProgress( - _sleepHistoryRepository.getSleepSequences(), _selectedSequences)); + _sleepSequenceRepository.getAll(), _selectedSequences)); } void _disableSelection() { _selectedSequences = null; _selectText.add('Select'); - emit(SleepSequenceHistoryLoaded( - _sleepHistoryRepository.getSleepSequences())); + emit(SleepSequenceHistoryLoaded(_sleepSequenceRepository.getAll())); } void toggleSelectSequenceForDeletion(SleepSequenceStats sequence) { @@ -60,11 +58,13 @@ class SleepSequenceHistoryCubit extends Cubit { } emit(SleepSequenceHistoryEditInProgress( - _sleepHistoryRepository.getSleepSequences(), _selectedSequences)); + _sleepSequenceRepository.getAll(), _selectedSequences)); } void deleteSelected() { - _sleepHistoryRepository.deleteSleepSequences(_selectedSequences); + _sleepSequenceRepository.delete( + (state as SleepSequenceHistoryEditInProgress).history, + _selectedSequences); _disableSelection(); } diff --git a/mobile/lib/src/database.dart b/mobile/lib/src/database.dart new file mode 100644 index 00000000..f971c435 --- /dev/null +++ b/mobile/lib/src/database.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:polydodo/src/domain/sleep_sequence/hive_sleep_sequence_stats.dart'; + +Future initDatabase() async { + WidgetsFlutterBinding.ensureInitialized(); + var appDocDir = await getApplicationDocumentsDirectory(); + await Hive.initFlutter(appDocDir.path + '/hive'); + Hive.registerAdapter(HiveSleepSequenceStatsAdapter()); +} diff --git a/mobile/lib/src/domain/sleep_sequence/hive_sleep_sequence_stats.dart b/mobile/lib/src/domain/sleep_sequence/hive_sleep_sequence_stats.dart new file mode 100644 index 00000000..75c31d17 --- /dev/null +++ b/mobile/lib/src/domain/sleep_sequence/hive_sleep_sequence_stats.dart @@ -0,0 +1,44 @@ +import 'package:hive/hive.dart'; + +part 'hive_sleep_sequence_stats.g.dart'; // Name of the TypeAdapter that we will generate in the future + +@HiveType(typeId: 1) +class HiveSleepSequenceStats { + @HiveField(0) + DateTime recordingStart; + + @HiveField(1) + DateTime recordingEnd; + + @HiveField(2) + int effectiveSleepTimeInSeconds; + + @HiveField(3) + double sleepEfficiency; + + @HiveField(4) + int sleepLatency; + + @HiveField(5) + int wasoInSeconds; + + @HiveField(6) + int awakenings; + + @HiveField(7) + int remLatency; + + @HiveField(8) + int numberTransitions; + + HiveSleepSequenceStats( + this.recordingStart, + this.recordingEnd, + this.effectiveSleepTimeInSeconds, + this.sleepEfficiency, + this.sleepLatency, + this.wasoInSeconds, + this.awakenings, + this.remLatency, + this.numberTransitions); +} diff --git a/mobile/lib/src/domain/sleep_sequence/i_sleep_sequence_repository.dart b/mobile/lib/src/domain/sleep_sequence/i_sleep_sequence_repository.dart index 53001468..f24d3f9a 100644 --- a/mobile/lib/src/domain/sleep_sequence/i_sleep_sequence_repository.dart +++ b/mobile/lib/src/domain/sleep_sequence/i_sleep_sequence_repository.dart @@ -1,7 +1,10 @@ import 'package:polydodo/src/domain/sleep_sequence/sleep_sequence_stats.dart'; abstract class ISleepSequenceRepository { - List getSleepSequences(); + List getAll(); - void deleteSleepSequences(List sequence); + void store(List sequence); + + void delete(List sequences, + List sequencesToDelete); } diff --git a/mobile/lib/src/domain/sleep_sequence/sleep_sequence_stats.dart b/mobile/lib/src/domain/sleep_sequence/sleep_sequence_stats.dart index 18ed13ee..283f3a00 100644 --- a/mobile/lib/src/domain/sleep_sequence/sleep_sequence_stats.dart +++ b/mobile/lib/src/domain/sleep_sequence/sleep_sequence_stats.dart @@ -4,10 +4,10 @@ import 'package:flutter/foundation.dart'; class SleepSequenceStats extends Entity { final DateTimeRange recordingTime; - final DateTime effectiveSleepTime; + final Duration effectiveSleepTime; final double sleepEfficiency; final int sleepLatency; - final DateTime waso; + final Duration waso; final int awakenings; final int remLatency; final int numberTransitions; diff --git a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index 156cd279..fa2d74b3 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -57,11 +57,14 @@ class EEGDataRepository implements IEEGDataRepository { @override Future stopRecordingFromStream() async { - // todo: move save future to another file unawaited(_currentTransformerStream.cancel()); if (_recordingData == null) return; + _saveRecordingToFile(); + } + + void _saveRecordingToFile() async { final directory = await getExternalStorageDirectory(); final pathOfTheFileToWrite = directory.path + '/' + _recordingData.fileName + '.txt'; diff --git a/mobile/lib/src/infrastructure/constants.dart b/mobile/lib/src/infrastructure/constants.dart index 58ebdd21..b147e28c 100644 --- a/mobile/lib/src/infrastructure/constants.dart +++ b/mobile/lib/src/infrastructure/constants.dart @@ -75,3 +75,6 @@ const OPEN_BCI_CYTON_HEADER = [ ' Timestamp (Formatted)' ] ]; + +const POLYDODO_BOX = 'Polydodo'; +const SLEEP_SEQUENCES_LIST_KEY = 'sequence_list'; diff --git a/mobile/lib/src/infrastructure/sleep_history/mock_data.dart b/mobile/lib/src/infrastructure/sleep_history/mock_data.dart index f4719e87..2ebb4216 100644 --- a/mobile/lib/src/infrastructure/sleep_history/mock_data.dart +++ b/mobile/lib/src/infrastructure/sleep_history/mock_data.dart @@ -5,21 +5,21 @@ import 'package:polydodo/src/domain/unique_id.dart'; SleepSequenceStats mock_data_1 = SleepSequenceStats( id: UniqueId.from('test'), awakenings: 3, - effectiveSleepTime: DateTime.now(), + effectiveSleepTime: Duration(minutes: 59), numberTransitions: 5, recordingTime: DateTimeRange(start: DateTime.now(), end: DateTime.now()), remLatency: 20, sleepEfficiency: 20.0, sleepLatency: 10, - waso: DateTime.now()); + waso: Duration(minutes: 59)); SleepSequenceStats mock_data_2 = SleepSequenceStats( id: UniqueId.from('test2'), awakenings: 100, - effectiveSleepTime: DateTime.now(), + effectiveSleepTime: Duration(minutes: 100), numberTransitions: 5, recordingTime: DateTimeRange(start: DateTime.now(), end: DateTime.now()), remLatency: 20, sleepEfficiency: 20.0, sleepLatency: 10, - waso: DateTime.now()); + waso: Duration(minutes: 100)); diff --git a/mobile/lib/src/infrastructure/sleep_history/mock_sleep_sequence_repository.dart b/mobile/lib/src/infrastructure/sleep_history/mock_sleep_sequence_repository.dart new file mode 100644 index 00000000..446fb53a --- /dev/null +++ b/mobile/lib/src/infrastructure/sleep_history/mock_sleep_sequence_repository.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:polydodo/src/domain/sleep_sequence/i_sleep_sequence_repository.dart'; +import 'package:polydodo/src/domain/sleep_sequence/sleep_sequence_stats.dart'; +import 'package:polydodo/src/infrastructure/sleep_history/mock_data.dart'; + +class MockSleepSequenceRepository implements ISleepSequenceRepository { + final sequenceStreamController = StreamController(); + List _sleepSequencesPersistency = []; + + MockSleepSequenceRepository() { + _sleepSequencesPersistency.add(mock_data_1); + + _sleepSequencesPersistency.add(mock_data_2); + } + + @override + List getAll() => _sleepSequencesPersistency; + + @override + void store(List sequenceList) { + _sleepSequencesPersistency = sequenceList; + } + + @override + void delete(List sequences, + List sequencesToDelete) { + for (var sequence in sequences) { + _sleepSequencesPersistency.remove(sequence); + } + } +} diff --git a/mobile/lib/src/infrastructure/sleep_history/sleep_history_repository.dart b/mobile/lib/src/infrastructure/sleep_history/sleep_history_repository.dart deleted file mode 100644 index 4ead4c9e..00000000 --- a/mobile/lib/src/infrastructure/sleep_history/sleep_history_repository.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'dart:async'; - -import 'package:polydodo/src/domain/sleep_sequence/i_sleep_sequence_repository.dart'; -import 'package:polydodo/src/domain/sleep_sequence/sleep_sequence_stats.dart'; -import 'package:polydodo/src/infrastructure/sleep_history/mock_data.dart'; - -class SleepHistoryRepository implements ISleepSequenceRepository { - final List _sleepHistoryPersistency = []; - final sequenceStreamController = StreamController(); - - SleepHistoryRepository() { - _sleepHistoryPersistency.add(mock_data_1); - - _sleepHistoryPersistency.add(mock_data_2); - } - - @override - List getSleepSequences() => _sleepHistoryPersistency; - - @override - void deleteSleepSequences(List sequences) { - for (var sequence in sequences) { - _sleepHistoryPersistency.remove(sequence); - } - } -} diff --git a/mobile/lib/src/infrastructure/sleep_history/sleep_sequence_repository.dart b/mobile/lib/src/infrastructure/sleep_history/sleep_sequence_repository.dart new file mode 100644 index 00000000..2b86cc19 --- /dev/null +++ b/mobile/lib/src/infrastructure/sleep_history/sleep_sequence_repository.dart @@ -0,0 +1,87 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:polydodo/src/domain/sleep_sequence/hive_sleep_sequence_stats.dart'; +import 'package:polydodo/src/domain/sleep_sequence/i_sleep_sequence_repository.dart'; +import 'package:polydodo/src/domain/sleep_sequence/sleep_sequence_stats.dart'; +import 'package:polydodo/src/domain/unique_id.dart'; +import 'package:polydodo/src/infrastructure/constants.dart'; + +class SleepSequenceRepository implements ISleepSequenceRepository { + final sequenceStreamController = StreamController(); + Box _sleepSequencesListBox; + + SleepSequenceRepository() { + _loadHiveBox(); + } + + void _loadHiveBox() async { + _sleepSequencesListBox = await Hive.openBox(POLYDODO_BOX); + } + + @override + List getAll() { + return _parseHiveSleepSequenceList( + _sleepSequencesListBox.get(SLEEP_SEQUENCES_LIST_KEY) ?? []); + } + + @override + void store(List sequence) { + _sleepSequencesListBox.put( + SLEEP_SEQUENCES_LIST_KEY, _parseSleepSequenceList(sequence)); + } + + @override + void delete(List sequences, + List sequencesToDelete) { + for (var sequence in sequencesToDelete) { + sequences.remove(sequence); + } + + store(sequences); + } + + List _parseHiveSleepSequenceList(var historyList) { + // ignore: omit_local_variable_types + List list = []; + + for (var sequence in historyList) { + list.add(SleepSequenceStats( + id: UniqueId.from(sequence.recordingStart.toString()), + awakenings: sequence.awakenings, + effectiveSleepTime: + Duration(seconds: sequence.effectiveSleepTimeInSeconds), + numberTransitions: sequence.numberTransitions, + recordingTime: DateTimeRange( + start: sequence.recordingStart, end: sequence.recordingEnd), + remLatency: sequence.remLatency, + sleepEfficiency: sequence.sleepEfficiency, + sleepLatency: sequence.sleepLatency, + waso: Duration(seconds: sequence.wasoInSeconds))); + } + + return list; + } + + List _parseSleepSequenceList( + List historyList) { + // ignore: omit_local_variable_types + List list = []; + + for (var sequence in historyList) { + list.add(HiveSleepSequenceStats( + sequence.recordingTime.start, + sequence.recordingTime.end, + sequence.effectiveSleepTime.inSeconds, + sequence.sleepEfficiency, + sequence.sleepLatency, + sequence.waso.inSeconds, + sequence.awakenings, + sequence.remLatency, + sequence.numberTransitions)); + } + + return list; + } +} diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 91c91893..1920fd94 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -8,10 +8,10 @@ import 'package:polydodo/src/domain/acquisition_device/device_locator_service.da 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 'package:polydodo/src/domain/sleep_sequence/i_sleep_sequence_repository.dart'; +import 'package:polydodo/src/infrastructure/connection_repositories/serial_repository.dart'; import 'package:polydodo/src/infrastructure/connection_repositories/bluetooth_repository.dart'; import 'package:polydodo/src/infrastructure/connection_repositories/eeg_data_repository.dart'; -import 'package:polydodo/src/infrastructure/connection_repositories/serial_repository.dart'; -import 'package:polydodo/src/infrastructure/sleep_history/sleep_history_repository.dart'; +import 'package:polydodo/src/infrastructure/sleep_history/sleep_sequence_repository.dart'; /// Private GetIt instance as we want all DI to be performed here in this file final _serviceLocator = GetIt.asNewInstance(); @@ -21,7 +21,7 @@ void registerServices() { DeviceLocatorService(BluetoothRepository(), SerialRepository())); _serviceLocator.registerSingleton(EEGDataRepository()); _serviceLocator - .registerSingleton(SleepHistoryRepository()); + .registerSingleton(SleepSequenceRepository()); } /// This function creates all the BlocProviders used in this app diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index d038824d..eb3ce0e1 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -225,6 +225,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.9" + dartx: + dependency: transitive + description: + name: dartx + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" equatable: dependency: "direct main" description: @@ -271,7 +278,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "6.0.6" + version: "6.1.1" flutter_launcher_icons: dependency: "direct dev" description: @@ -285,7 +292,7 @@ packages: name: flutter_reactive_ble url: "https://pub.dartlang.org" source: hosted - version: "2.6.1+1" + version: "2.7.1" flutter_test: dependency: "direct dev" description: flutter @@ -331,6 +338,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.4+1" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.2" http_multi_server: dependency: transitive description: @@ -351,7 +372,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.18" + version: "2.1.19" intl: dependency: "direct main" description: @@ -568,7 +589,7 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.1.4+1" + version: "2.1.5" share: dependency: "direct main" description: @@ -700,6 +721,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.19-nullsafety.2" + time: + dependency: transitive + description: + name: time + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" timing: dependency: transitive description: @@ -755,7 +783,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "1.7.3" + version: "1.7.4" xdg_directories: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a1ab84b4..4925c1d7 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: flutter_reactive_ble: ^2.5.2 get_it: ^4.0.4 hive: ^1.4.4 + hive_flutter: ^0.3.1 intl: ^0.16.1 meta: ^1.1.8 path_provider: ^1.6.16 @@ -51,6 +52,8 @@ dev_dependencies: flutter_launcher_icons: "^0.7.0" flutter_test: sdk: flutter + hive_generator: ^0.8.2 + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter.