diff --git a/mobile/.vscode/launch.json b/mobile/.vscode/launch.json index 7f5fbfee..bf6b31d8 100644 --- a/mobile/.vscode/launch.json +++ b/mobile/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "mobile", + "name": "Mobile debug", "program": "lib/main.dart", "request": "launch", "type": "dart", diff --git a/mobile/lib/src/application/navdrawer/navdrawer_state.dart b/mobile/lib/src/application/navdrawer/navdrawer_state.dart deleted file mode 100644 index 9d3063bb..00000000 --- a/mobile/lib/src/application/navdrawer/navdrawer_state.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of 'navdrawer_bloc.dart'; - -enum NavdrawerState { - DashBoard, - RecordSleep, - History, - BluetoothSelector, - SleepHistory, - NightStats -} diff --git a/mobile/lib/src/application/settings/settings_cubit.dart b/mobile/lib/src/application/settings/settings_cubit.dart new file mode 100644 index 00000000..63c0b6a8 --- /dev/null +++ b/mobile/lib/src/application/settings/settings_cubit.dart @@ -0,0 +1,56 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:polydodo/src/domain/settings/i_settings_repository.dart'; +import 'package:polydodo/src/domain/settings/settings.dart'; + +part 'settings_state.dart'; + +class SettingsCubit extends Cubit { + final ISettingsRepository _repository; + + SettingsCubit(this._repository) : super(SettingsLoadInProgress()) { + getSettings(); + } + + Future getSettings() async { + await emit(SettingsLoadSuccess(await _repository.read())); + } + + Future setAge(int newAge) async { + if (state is SettingsLoadSuccess) { + emit( + SettingsLoadSuccess( + await _repository.store( + (state as SettingsLoadSuccess).settings.copyWith(age: newAge), + ), + ), + ); + } + } + + Future setSex(Sex newSex) async { + if (state is SettingsLoadSuccess) { + emit( + SettingsLoadSuccess( + await _repository.store( + (state as SettingsLoadSuccess).settings.copyWith(sex: newSex), + ), + ), + ); + } + } + + Future setServerAddress(String newServerAddress) async { + if (state is SettingsLoadSuccess) { + emit( + SettingsLoadSuccess( + await _repository.store( + (state as SettingsLoadSuccess) + .settings + .copyWith(serverAddress: newServerAddress), + ), + ), + ); + } + } +} diff --git a/mobile/lib/src/application/settings/settings_state.dart b/mobile/lib/src/application/settings/settings_state.dart new file mode 100644 index 00000000..9188a13a --- /dev/null +++ b/mobile/lib/src/application/settings/settings_state.dart @@ -0,0 +1,24 @@ +part of 'settings_cubit.dart'; + +abstract class SettingsState extends Equatable { + const SettingsState(); + + @override + List get props => []; +} + +class SettingsLoadInProgress extends SettingsState {} + +class SettingsLoadSuccess extends SettingsState { + final Settings settings; + + const SettingsLoadSuccess(this.settings); + + @override + List get props => [settings]; + + @override + String toString() => 'SettingsLoadSuccess { settings: $settings }'; +} + +class SettingsLoadFailure extends SettingsState {} diff --git a/mobile/lib/src/common/constants.dart b/mobile/lib/src/common/constants.dart new file mode 100644 index 00000000..b49bddfb --- /dev/null +++ b/mobile/lib/src/common/constants.dart @@ -0,0 +1,11 @@ +RegExp IP_ADDRESS_REGEX = RegExp( + r'^(?=\d+\.\d+\.\d+\.\d+$)(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\.?){4}$', +); + +const DEFAULT_AGE = 30; +const DEFAULT_SERVER_ADDRESS = '192.168.1.1'; + +const MIN_AGE = 12; +const MAX_AGE = 125; +final MIN_BIRTH_DATE = DateTime.now().subtract(Duration(days: 365 * MAX_AGE)); +final MAX_BIRTH_DATE = DateTime.now().subtract(Duration(days: 365 * MIN_AGE)); diff --git a/mobile/lib/src/common/settings_keys.dart b/mobile/lib/src/common/settings_keys.dart new file mode 100644 index 00000000..9263e29c --- /dev/null +++ b/mobile/lib/src/common/settings_keys.dart @@ -0,0 +1,5 @@ +library settings_keys; + +const String AGE = 'age'; +const String SERVER_ADDRESS = 'aerverAdress'; +const String SEX = 'serverAdress'; diff --git a/mobile/lib/src/domain/settings/i_settings_repository.dart b/mobile/lib/src/domain/settings/i_settings_repository.dart new file mode 100644 index 00000000..fa250fc6 --- /dev/null +++ b/mobile/lib/src/domain/settings/i_settings_repository.dart @@ -0,0 +1,7 @@ +import 'package:polydodo/src/domain/settings/settings.dart'; + +abstract class ISettingsRepository { + Future read(); + + Future store(Settings newSettings); +} diff --git a/mobile/lib/src/domain/settings/settings.dart b/mobile/lib/src/domain/settings/settings.dart new file mode 100644 index 00000000..cad4cf8f --- /dev/null +++ b/mobile/lib/src/domain/settings/settings.dart @@ -0,0 +1,50 @@ +import 'package:equatable/equatable.dart'; +import 'package:polydodo/src/common/constants.dart'; + +part 'sex.dart'; + +class Settings extends Equatable { + final int age; + final String serverAddress; + final Sex sex; + + factory Settings({int age, String serverAddress, Sex sex}) { + age = age ?? DEFAULT_AGE; + serverAddress = serverAddress ?? DEFAULT_SERVER_ADDRESS; + sex = sex ?? Sex.NotSet; + if (age < MIN_AGE || age > MAX_AGE) { + throw AgeNotInValidIntervalException( + "L'âge configuré doit être entre 12 et 125 ans."); + } + + if (!IP_ADDRESS_REGEX.hasMatch(serverAddress)) { + throw InvalidIPAddressException( + "L'adresse du serveur configurée doit être une adresse de format IPv4."); + } + + return Settings._internal(age, serverAddress, sex); + } + + Settings._internal(this.age, this.serverAddress, this.sex); + + @override + List get props => [age, serverAddress, sex]; + + Settings copyWith({int age, String serverAddress, Sex sex}) { + return Settings( + age: age ?? this.age, + serverAddress: serverAddress ?? this.serverAddress, + sex: sex ?? this.sex, + ); + } +} + +class InvalidIPAddressException implements Exception { + String cause; + InvalidIPAddressException(this.cause); +} + +class AgeNotInValidIntervalException implements Exception { + String cause; + AgeNotInValidIntervalException(this.cause); +} diff --git a/mobile/lib/src/domain/settings/sex.dart b/mobile/lib/src/domain/settings/sex.dart new file mode 100644 index 00000000..52a8b12d --- /dev/null +++ b/mobile/lib/src/domain/settings/sex.dart @@ -0,0 +1,3 @@ +part of 'settings.dart'; + +enum Sex { NotSet, Male, Female } diff --git a/mobile/lib/src/infrastructure/settings_repository/settings_repository.dart b/mobile/lib/src/infrastructure/settings_repository/settings_repository.dart new file mode 100644 index 00000000..f245cf9d --- /dev/null +++ b/mobile/lib/src/infrastructure/settings_repository/settings_repository.dart @@ -0,0 +1,31 @@ +import 'package:polydodo/src/common/settings_keys.dart' as settings_keys; +import 'package:polydodo/src/domain/settings/i_settings_repository.dart'; +import 'package:polydodo/src/domain/settings/settings.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SettingsRepository extends ISettingsRepository { + SharedPreferences _prefs; + + SettingsRepository(); + + @override + Future read() async { + _prefs = (await SharedPreferences.getInstance()); + + return Settings( + age: _prefs.getInt(settings_keys.AGE), + serverAddress: _prefs.getString(settings_keys.SERVER_ADDRESS), + sex: Sex.values[(_prefs.getInt(settings_keys.SEX)) ?? Sex.NotSet.index], + ); + } + + @override + Future store(Settings newSettings) async { + await _prefs.setInt(settings_keys.AGE, newSettings.age); + await _prefs.setString( + settings_keys.SERVER_ADDRESS, newSettings.serverAddress); + await _prefs.setInt(settings_keys.SEX, newSettings.sex.index); + + return newSettings; + } +} diff --git a/mobile/lib/src/locator.dart b/mobile/lib/src/locator.dart index 1920fd94..ec02c2f6 100644 --- a/mobile/lib/src/locator.dart +++ b/mobile/lib/src/locator.dart @@ -2,16 +2,21 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.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/application/settings/settings_cubit.dart'; import 'package:polydodo/src/application/sleep_sequence_history/sleep_sequence_history_cubit.dart'; import 'package:polydodo/src/application/sleep_sequence_stats/sleep_sequence_stats_cubit.dart'; import 'package:polydodo/src/domain/acquisition_device/device_locator_service.dart'; 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/settings/i_settings_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/sleep_history/sleep_sequence_repository.dart'; +import 'package:polydodo/src/infrastructure/settings_repository/settings_repository.dart'; + +import 'infrastructure/settings_repository/settings_repository.dart'; /// Private GetIt instance as we want all DI to be performed here in this file final _serviceLocator = GetIt.asNewInstance(); @@ -22,6 +27,7 @@ void registerServices() { _serviceLocator.registerSingleton(EEGDataRepository()); _serviceLocator .registerSingleton(SleepSequenceRepository()); + _serviceLocator.registerSingleton(SettingsRepository()); } /// This function creates all the BlocProviders used in this app @@ -43,4 +49,8 @@ List createBlocProviders() => [ create: (context) => SleepSequenceHistoryCubit( _serviceLocator.get(), BlocProvider.of(context))), + BlocProvider( + create: (context) => + SettingsCubit(_serviceLocator.get()), + ), ]; diff --git a/mobile/lib/src/presentation/navigation/navdrawer_tabs.dart b/mobile/lib/src/presentation/navigation/navdrawer_tabs.dart index 1e1c0689..c547e540 100644 --- a/mobile/lib/src/presentation/navigation/navdrawer_tabs.dart +++ b/mobile/lib/src/presentation/navigation/navdrawer_tabs.dart @@ -1,7 +1,11 @@ +part of 'navdrawer_widget.dart'; + enum NavdrawerTab { + BluetoothSelector, Dashboard, + History, RecordSleep, + Settings, DeviceSelector, - History, SleepSequenceStats } diff --git a/mobile/lib/src/presentation/navigation/navdrawer_widget.dart b/mobile/lib/src/presentation/navigation/navdrawer_widget.dart index 5dfd13ec..1fa6c270 100644 --- a/mobile/lib/src/presentation/navigation/navdrawer_widget.dart +++ b/mobile/lib/src/presentation/navigation/navdrawer_widget.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:polydodo/src/presentation/navigation/routes/router.gr.dart'; -import 'navdrawer_tabs.dart'; +part 'navdrawer_tabs.dart'; class NavDrawer extends StatelessWidget { static const name = 'appDrawerRoute'; @@ -48,6 +48,13 @@ class NavDrawer extends StatelessWidget { tab: NavdrawerTab.History, context: context, ), + _createDrawerItem( + icon: Icons.settings, + text: 'Settings', + route: Routes.settingsPage, + tab: NavdrawerTab.Settings, + context: context, + ), ], ), ); diff --git a/mobile/lib/src/presentation/navigation/routes/router.dart b/mobile/lib/src/presentation/navigation/routes/router.dart index bf295daa..6eed9ccf 100644 --- a/mobile/lib/src/presentation/navigation/routes/router.dart +++ b/mobile/lib/src/presentation/navigation/routes/router.dart @@ -5,6 +5,7 @@ import 'package:polydodo/src/presentation/pages/device_selector/device_selector_ import 'package:polydodo/src/presentation/pages/record_sleep/record_sleep_guide_page.dart'; import 'package:polydodo/src/presentation/pages/record_sleep/record_sleep_recording_page.dart'; import 'package:polydodo/src/presentation/pages/record_sleep/record_sleep_validate_page.dart'; +import 'package:polydodo/src/presentation/pages/settings/settings_page.dart'; import 'package:polydodo/src/presentation/pages/sleep_history_page/sleep_history_page.dart'; import 'package:polydodo/src/presentation/pages/sleep_sequence_stats_page/sleep_sequence_stats_page.dart'; @@ -33,5 +34,7 @@ import 'package:polydodo/src/presentation/pages/sleep_sequence_stats_page/sleep_ CustomRoute( page: SleepSequenceStatsPage, transitionsBuilder: TransitionsBuilders.fadeIn), + CustomRoute( + page: SettingsPage, transitionsBuilder: TransitionsBuilders.fadeIn), ]) class $Router {} diff --git a/mobile/lib/src/presentation/pages/dashboard/dashboard_page.dart b/mobile/lib/src/presentation/pages/dashboard/dashboard_page.dart index cd6e38b9..f53391c3 100644 --- a/mobile/lib/src/presentation/pages/dashboard/dashboard_page.dart +++ b/mobile/lib/src/presentation/pages/dashboard/dashboard_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; import 'package:polydodo/src/presentation/pages/dashboard/sliver_app_bar_title.dart'; diff --git a/mobile/lib/src/presentation/pages/device_selector/device_selector_page.dart b/mobile/lib/src/presentation/pages/device_selector/device_selector_page.dart index 5303d85c..5fe6d58d 100644 --- a/mobile/lib/src/presentation/pages/device_selector/device_selector_page.dart +++ b/mobile/lib/src/presentation/pages/device_selector/device_selector_page.dart @@ -3,7 +3,6 @@ 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/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; import 'package:polydodo/src/presentation/navigation/routes/router.gr.dart'; diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart index f7b0c3ac..74b60f25 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:polydodo/src/application/blocs.dart'; import 'package:polydodo/src/constants.dart'; -import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; import 'package:polydodo/src/presentation/navigation/routes/router.gr.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart index 68306b51..2c23f616 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart @@ -2,7 +2,6 @@ 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'; -import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; class RecordSleepRecordingPage extends StatelessWidget { diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart index f0a49bc5..504834f4 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart @@ -3,7 +3,6 @@ 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'; -import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; import 'package:polydodo/src/presentation/navigation/routes/router.gr.dart'; import 'package:polydodo/src/presentation/pages/record_sleep/signal_section.dart'; diff --git a/mobile/lib/src/presentation/pages/settings/settings_page.dart b/mobile/lib/src/presentation/pages/settings/settings_page.dart new file mode 100644 index 00000000..04166ae7 --- /dev/null +++ b/mobile/lib/src/presentation/pages/settings/settings_page.dart @@ -0,0 +1,196 @@ +import 'dart:ui'; +import 'package:enum_to_string/enum_to_string.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/settings/settings_cubit.dart'; +import 'package:polydodo/src/common/constants.dart'; +import 'package:polydodo/src/domain/settings/settings.dart'; +import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; +import 'package:polydodo/src/presentation/widgets/loading_indicator.dart'; +import 'package:settings_ui/settings_ui.dart'; + +class SettingsPage extends StatefulWidget { + SettingsPage({Key key}) : super(key: key); + + @override + _SettingsPageState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Settings')), + drawer: NavDrawer(activeTab: NavdrawerTab.Settings), + body: BlocBuilder( + builder: (context, state) { + return state is SettingsLoadSuccess + ? SettingsList( + sections: [ + SettingsSection( + title: 'Personnal informations', + tiles: [ + _buildDatePickerSettingTile( + 'Age', + 'In years', + Icons.cake, + (datePicked) => + BlocProvider.of(context).setAge( + DateTime.now() + .difference(datePicked) + .inDays ~/ + 365), + context, + state, + ), + _buildSelectSettingTile( + 'Sex', + 'Your biological sex', + Sex.values, + Icons.face, + (newSex) => BlocProvider.of(context) + .setSex(newSex), + state, + ), + _buildServerAdressSettingTile(context, state), + ], + ), + ], + ) + : LoadingIndicator(); + }, + ), + ); + } +} + +SettingsTile _buildSelectSettingTile( + String title, + String substitle, + dynamic settingOptions, + IconData icon, + Function(dynamic) onSelected, + SettingsState state, +) { + return SettingsTile( + title: title, + subtitle: substitle, + leading: Icon(icon), + trailing: SettingsPopupMenuButton( + savedSetting: (state as SettingsLoadSuccess).settings.sex, + settingOptions: settingOptions, + onSelected: onSelected, + ), + ); +} + +SettingsTile _buildDatePickerSettingTile( + String title, + String subtitle, + IconData icon, + Function(dynamic) onDatePicked, + BuildContext context, + SettingsState state, +) { + return SettingsTile( + title: 'Age', + subtitle: 'In years', + leading: Icon(icon), + trailing: TextButton( + child: Text( + (state as SettingsLoadSuccess).settings.age == null + ? 'Not Set' + : (state as SettingsLoadSuccess).settings.age.toString(), + ), + onPressed: () => _showDatePicker(onDatePicked, context)), + ); +} + +void _showDatePicker( + Function(dynamic) onDatePicked, + BuildContext context, +) async { + final datePicked = await showDatePicker( + context: context, + initialEntryMode: DatePickerEntryMode.calendar, + initialDatePickerMode: DatePickerMode.year, + initialDate: MAX_BIRTH_DATE, + firstDate: MIN_BIRTH_DATE, + lastDate: MAX_BIRTH_DATE, + helpText: 'Select birthdate'); + + if (datePicked != null && datePicked != DateTime.now()) { + onDatePicked(datePicked); + } +} + +SettingsTile _buildServerAdressSettingTile( + BuildContext context, SettingsState state) { + return SettingsTile( + title: 'Server Address', + subtitle: 'The url for classification', + leading: Icon(Icons.dns), + trailing: Container( + width: 100, + child: TextField( + controller: TextEditingController() + ..text = (state as SettingsLoadSuccess).settings.serverAddress, + onSubmitted: (newAdress) { + try { + BlocProvider.of(context).setServerAddress(newAdress); + } on InvalidIPAddressException catch (e) { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text(e.cause), duration: Duration(seconds: 3))); + } + }, + ), + ), + ); +} + +class SettingsPopupMenuButton extends StatelessWidget { + SettingsPopupMenuButton({ + Key key, + @required this.savedSetting, + @required this.settingOptions, + @required this.onSelected, + }) : super(key: key); + + final dynamic savedSetting; + final List settingOptions; + final Function(dynamic) onSelected; + final TextStyle activeStyle = TextStyle(fontWeight: FontWeight.bold); + final TextStyle defaultStyle = TextStyle(); + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + child: + // ignore: missing_required_param + TextButton( + child: Text( + EnumToString.convertToString(savedSetting, camelCase: true), + ), + style: ButtonStyle( + foregroundColor: + MaterialStateProperty.all(Theme.of(context).colorScheme.primary), + ), + ), + onSelected: onSelected, + itemBuilder: (BuildContext context) => + _buildPopupItemList(settingOptions), + ); + } + + List _buildPopupItemList(List values) { + return [ + for (var setting + in values.sublist(1)) // Only starting at 1 cause 0 is 'Not Set' + PopupMenuItem( + value: setting, + child: Text(EnumToString.convertToString(setting, camelCase: true), + style: savedSetting == setting ? activeStyle : defaultStyle), + ), + ]; + } +} diff --git a/mobile/lib/src/presentation/pages/sleep_history_page/sleep_history_page.dart b/mobile/lib/src/presentation/pages/sleep_history_page/sleep_history_page.dart index 278fbb25..9e9012e3 100644 --- a/mobile/lib/src/presentation/pages/sleep_history_page/sleep_history_page.dart +++ b/mobile/lib/src/presentation/pages/sleep_history_page/sleep_history_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:polydodo/src/application/sleep_sequence_history/sleep_sequence_history_cubit.dart'; import 'package:polydodo/src/application/sleep_sequence_history/sleep_sequence_history_state.dart'; -import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; import 'package:polydodo/src/presentation/pages/sleep_history_page/app_bar.dart'; import 'package:polydodo/src/presentation/pages/sleep_history_page/sleep_history_list.dart'; diff --git a/mobile/lib/src/presentation/widgets/loading_indicator.dart b/mobile/lib/src/presentation/widgets/loading_indicator.dart new file mode 100644 index 00000000..2ecef34b --- /dev/null +++ b/mobile/lib/src/presentation/widgets/loading_indicator.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class LoadingIndicator extends StatelessWidget { + LoadingIndicator({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: CircularProgressIndicator(), + ); + } +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index b1f1b5fa..11df7bae 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -232,6 +232,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.5.0" + enum_to_string: + dependency: "direct main" + description: + name: enum_to_string + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.13" equatable: dependency: "direct main" description: @@ -590,6 +597,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.5" + settings_ui: + dependency: "direct main" + description: + name: settings_ui + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" share: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 97afa13b..b7b1d94b 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: csv: ^4.0.3 cupertino_icons: ^0.1.3 equatable: ^1.2.5 + enum_to_string: ^1.0.13 flutter_bloc: ^6.0.5 flutter_reactive_ble: ^2.5.2 get_it: ^4.0.4 @@ -42,6 +43,7 @@ dependencies: pedantic: ^1.9.0 percent_indicator: '^2.1.8' share: ^0.6.5 + settings_ui: ^0.4.0 streaming_shared_preferences: ^1.0.1 url_launcher: ^5.7.10 usb_serial: ^0.2.4