From eaf355c1200ea21fd8fd935efcc8430849247117 Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 1 Feb 2023 12:53:26 +0300 Subject: [PATCH 01/25] Added AC drive widget --- example/assets/icons/ac_motor.png | Bin 0 -> 21202 bytes example/assets/icons/ac_motor_failure.png | Bin 0 -> 22614 bytes example/lib/pages/home/home_page.dart | 9 ++ example/lib/pages/process/process_page.dart | 31 +++++++ example/pubspec.yaml | 3 +- lib/hmi_widgets.dart | 18 ++-- .../electrical/drive/ac_drive_widget.dart | 79 ++++++++++++++++++ 7 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 example/assets/icons/ac_motor.png create mode 100644 example/assets/icons/ac_motor_failure.png create mode 100644 example/lib/pages/process/process_page.dart create mode 100644 lib/src/process/electrical/drive/ac_drive_widget.dart diff --git a/example/assets/icons/ac_motor.png b/example/assets/icons/ac_motor.png new file mode 100644 index 0000000000000000000000000000000000000000..010fa4de6ca58813cabe87b5fe09a622ad835479 GIT binary patch literal 21202 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

%yCPp7D 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

QJsN8WykJq2R-Ko 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 P00000NkvXXu0mjf{32D3 literal 0 HcmV?d00001 diff --git a/example/lib/pages/home/home_page.dart b/example/lib/pages/home/home_page.dart index ed5bb4f..02ac48d 100644 --- a/example/lib/pages/home/home_page.dart +++ b/example/lib/pages/home/home_page.dart @@ -1,5 +1,6 @@ import 'package:example/pages/buttons/buttons_page.dart'; import 'package:example/pages/charts/charts_page.dart'; +import 'package:example/pages/process/ac_drive_page.dart'; import 'package:flutter/material.dart'; import 'home_menu_button.dart'; @@ -38,6 +39,14 @@ class HomePage extends StatelessWidget { const HomeMenuButton( text: 'Value Indicators', ), + HomeMenuButton( + text: 'AC Drive Widget', + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const ProccessPage(), + ), + ), + ), ], ), ); diff --git a/example/lib/pages/process/process_page.dart b/example/lib/pages/process/process_page.dart new file mode 100644 index 0000000..860a160 --- /dev/null +++ b/example/lib/pages/process/process_page.dart @@ -0,0 +1,31 @@ +import 'package:example/core/get_random_stream.dart'; +import 'package:flutter/material.dart'; +import 'package:hmi_widgets/hmi_widgets.dart'; +/// +class ProccessPage extends StatelessWidget { + const ProccessPage({super.key}); + // + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Process Widgets')), + body: ListView( + padding: const EdgeInsets.all(4.0), + children: [ + Column( + children: [ + const Text('AC Drive'), + AcDriveWidget( + stream: getRandomDataPointStream( + (random) => random.nextInt(4), + ).asBroadcastStream(), + acMotorIcon: Image.asset('assets/icons/ac_motor.png'), + acMotorFailureIcon: Image.asset('assets/icons/ac_motor_failure.png'), + ), + ], + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 83a8f78..d4570bb 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -32,7 +32,8 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - + assets: + - assets/icons/ # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/lib/hmi_widgets.dart b/lib/hmi_widgets.dart index e4b0345..e8e9f42 100644 --- a/lib/hmi_widgets.dart +++ b/lib/hmi_widgets.dart @@ -1,21 +1,21 @@ library hmi_widgets; - +// // Theme export 'src/theme/app_theme.dart'; - +// // Dialogs export 'src/dialogs/complete_dialog.dart'; export 'src/dialogs/delete_dialog.dart'; export 'src/dialogs/failure_dialog.dart'; - +// // Buttons export 'src/buttons/circular_fab_widget.dart'; export 'src/buttons/control_button/control_button.dart'; export 'src/buttons/drop_down_control_button/drop_down_control_button.dart'; - +// // Popups export 'src/popups/popup_menu_button/popup_menu_button_custom.dart'; - +// // Indicators // Status indicators export 'src/indicators/status_indicators/alarmed_status_indicator_widget.dart'; @@ -24,12 +24,13 @@ export 'src/indicators/status_indicators/dps_icon_indicator.dart'; export 'src/indicators/status_indicators/invalid_status_indicator.dart'; export 'src/indicators/status_indicators/sps_icon_indicator.dart'; export 'src/indicators/status_indicators/status_indicator_widget.dart'; +// // Value indicators export 'src/indicators/value_indicators/circular_value_indicator.dart'; export 'src/indicators/value_indicators/linear_value_indicator.dart'; export 'src/indicators/value_indicators/text_value_indicator.dart'; export 'src/indicators/value_indicators/text_value_indicator_widget.dart'; - +// // Charts export 'src/charts/crane_load_chart/crane_load_chart.dart'; export 'src/charts/crane_load_chart/swl_data_cache.dart'; @@ -38,4 +39,7 @@ export 'src/charts/crane_load_chart/swl_data.dart'; export 'src/charts/crane_load_chart/crane_load_chart_legend_data.dart'; export 'src/charts/crane_load_chart/crane_load_chart_data.dart'; export 'src/charts/crane_position_chart/crane_position_chart.dart'; -export 'src/charts/live_chart/live_chart_widget.dart'; \ No newline at end of file +export 'src/charts/live_chart/live_chart_widget.dart'; +// +// Process +export 'src/process/electrical/drive/ac_drive_widget.dart'; \ No newline at end of file diff --git a/lib/src/process/electrical/drive/ac_drive_widget.dart b/lib/src/process/electrical/drive/ac_drive_widget.dart new file mode 100644 index 0000000..d7bb30d --- /dev/null +++ b/lib/src/process/electrical/drive/ac_drive_widget.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_widgets/src/indicators/status_indicators/dps_icon_indicator.dart'; +import 'package:hmi_widgets/src/indicators/status_indicators/invalid_status_indicator.dart'; +import 'package:hmi_widgets/src/indicators/status_indicators/status_indicator_widget.dart'; +import 'package:hmi_widgets/src/theme/app_theme.dart'; + +class AcDriveWidget extends StatelessWidget { + final Stream>? _stream; + final String? _caption; + final bool _disabled; + final Widget _acMotorIcon; + final Widget _acMotorFailureIcon; + /// + const AcDriveWidget({ + Key? key, + required Widget acMotorIcon, + required Widget acMotorFailureIcon, + Stream>? stream, + String? caption, + bool disabled = false, + }) : + _stream = stream, + _caption = caption, + _disabled = disabled, + _acMotorIcon = acMotorIcon, + _acMotorFailureIcon = acMotorFailureIcon, + super(key: key); + /// + @override + Widget build(BuildContext context) { + final caption = _caption; + final stateColors = Theme.of(context).stateColors; + return InvalidStatusIndicator( + stream: _stream, + stateColors: stateColors, + child: StatusIndicatorWidget( + width: 100, + height: 100, + disabled: _disabled, + indicator: DpsIconIndicator( + stream: _stream, + posUndefinedIcon: ColorFiltered( + colorFilter: ColorFilter.mode( + Theme.of(context).stateColors.invalid, + BlendMode.srcIn, + ), + child: _acMotorIcon, + ), + posOffIcon: ColorFiltered( + colorFilter: ColorFilter.mode( + Theme.of(context).stateColors.off, + BlendMode.srcIn, + ), + child: _acMotorIcon, + ), + posOnIcon: ColorFiltered( + colorFilter: ColorFilter.mode( + Theme.of(context).stateColors.on, + BlendMode.srcIn, + ), + child: _acMotorIcon, + ), + posTransientIcon: ColorFiltered( + colorFilter: ColorFilter.mode( + Theme.of(context).stateColors.error, + BlendMode.srcIn, + ), + child: _acMotorFailureIcon, + ), + ), + caption: (caption != null) + ? Text(caption, textAlign: TextAlign.center) + : null, + alignment: Alignment.center, + ), + ); + } +} \ No newline at end of file From 694f2f6d7cca45c4fd5df407aafaca24638a349b Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 1 Feb 2023 13:09:43 +0300 Subject: [PATCH 02/25] Fixed CraneLoadChart in example --- example/lib/pages/charts/charts_page.dart | 2 +- example/lib/pages/home/home_page.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/lib/pages/charts/charts_page.dart b/example/lib/pages/charts/charts_page.dart index 4bdb4dd..1dbeb26 100644 --- a/example/lib/pages/charts/charts_page.dart +++ b/example/lib/pages/charts/charts_page.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:hmi_widgets/hmi_widgets.dart'; /// class ChartsPage extends StatelessWidget { - final _swlLimitSet = const [0.3, 0.5, 0.7]; + final _swlLimitSet = const [0.3, 0.5, 0.7, 1.1]; final _swlColorSet = const [Colors.grey, Colors.green, Colors.blue, Colors.red]; final _swlNameSet = const ['Limit1', 'Limit2', 'Limit3', 'Limits4']; final _width = 450.0; diff --git a/example/lib/pages/home/home_page.dart b/example/lib/pages/home/home_page.dart index 02ac48d..7186ccc 100644 --- a/example/lib/pages/home/home_page.dart +++ b/example/lib/pages/home/home_page.dart @@ -1,6 +1,6 @@ import 'package:example/pages/buttons/buttons_page.dart'; import 'package:example/pages/charts/charts_page.dart'; -import 'package:example/pages/process/ac_drive_page.dart'; +import 'package:example/pages/process/process_page.dart'; import 'package:flutter/material.dart'; import 'home_menu_button.dart'; @@ -40,7 +40,7 @@ class HomePage extends StatelessWidget { text: 'Value Indicators', ), HomeMenuButton( - text: 'AC Drive Widget', + text: 'Process Widgets', onPressed: () => Navigator.of(context).push( MaterialPageRoute( builder: (_) => const ProccessPage(), From af3e8cf0535e786dda280de3e89c1a4ebfe66493 Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 1 Feb 2023 13:26:56 +0300 Subject: [PATCH 03/25] Added DateEditField --- .../pages/edit_fields/edit_fields_page.dart | 29 +++ example/lib/pages/home/home_page.dart | 9 + lib/hmi_widgets.dart | 5 +- lib/src/edit_field/date_edit_field.dart | 191 ++++++++++++++++++ 4 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 example/lib/pages/edit_fields/edit_fields_page.dart create mode 100644 lib/src/edit_field/date_edit_field.dart diff --git a/example/lib/pages/edit_fields/edit_fields_page.dart b/example/lib/pages/edit_fields/edit_fields_page.dart new file mode 100644 index 0000000..73add5a --- /dev/null +++ b/example/lib/pages/edit_fields/edit_fields_page.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:hmi_widgets/hmi_widgets.dart'; + +class EditFieldsPage extends StatelessWidget { + const EditFieldsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Edit Fields'), + ), + body: ListView( + padding: const EdgeInsets.all(4), + children: [ + Column( + children: const [ + Text('Date Edit Field'), + SizedBox( + width: 300, + child: DateEditField(), + ), + ], + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/example/lib/pages/home/home_page.dart b/example/lib/pages/home/home_page.dart index 7186ccc..2244429 100644 --- a/example/lib/pages/home/home_page.dart +++ b/example/lib/pages/home/home_page.dart @@ -1,5 +1,6 @@ import 'package:example/pages/buttons/buttons_page.dart'; import 'package:example/pages/charts/charts_page.dart'; +import 'package:example/pages/edit_fields/edit_fields_page.dart'; import 'package:example/pages/process/process_page.dart'; import 'package:flutter/material.dart'; @@ -47,6 +48,14 @@ class HomePage extends StatelessWidget { ), ), ), + HomeMenuButton( + text: 'Edit fields', + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const EditFieldsPage(), + ), + ), + ), ], ), ); diff --git a/lib/hmi_widgets.dart b/lib/hmi_widgets.dart index e8e9f42..d160729 100644 --- a/lib/hmi_widgets.dart +++ b/lib/hmi_widgets.dart @@ -42,4 +42,7 @@ export 'src/charts/crane_position_chart/crane_position_chart.dart'; export 'src/charts/live_chart/live_chart_widget.dart'; // // Process -export 'src/process/electrical/drive/ac_drive_widget.dart'; \ No newline at end of file +export 'src/process/electrical/drive/ac_drive_widget.dart'; +// +// Edit fields +export 'src/edit_field/date_edit_field.dart'; \ No newline at end of file diff --git a/lib/src/edit_field/date_edit_field.dart b/lib/src/edit_field/date_edit_field.dart new file mode 100644 index 0000000..22cd3e9 --- /dev/null +++ b/lib/src/edit_field/date_edit_field.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class DateEditField extends StatefulWidget { + final String? _label; + final void Function(DateTime?)? _onChanged; + final void Function(DateTime?)? _onComplete; + final void Function(DateTime?)? _onSubmitted; + /// + const DateEditField({ + Key? key, + String? label, + void Function(DateTime?)? onChanged, + void Function(DateTime?)? onComplete, + void Function(DateTime?)? onSubmitted, + }) : + _label = label, + _onChanged = onChanged, + _onComplete = onComplete, + _onSubmitted = onSubmitted, + super(key: key); + @override + State createState() { + return _DateEditFieldState( + label: _label, + onChanged: _onChanged, + onComplete: _onComplete, + onSubmitted: _onSubmitted, + ); + } +} + +class _DateEditFieldState extends State { + final String? _label; + final double _suffixPadding; + final void Function(DateTime?)? _onChanged; + final void Function(DateTime?)? _onComplete; + final void Function(DateTime?)? _onSubmitted; + AutovalidateMode _autovalidateMode = AutovalidateMode.disabled; + final TextEditingController _textController = TextEditingController(); + DateTime? _dateTime; + /// + _DateEditFieldState({ + String? label, + double suffixPadding = 8.0, + void Function(DateTime?)? onChanged, + void Function(DateTime?)? onComplete, + void Function(DateTime?)? onSubmitted, + }) : + _label = label, + _onChanged = onChanged, + _onComplete = onComplete, + _onSubmitted = onSubmitted, + _suffixPadding = suffixPadding, + super(); + @override + Widget build(BuildContext context) { + return TextFormField( + controller: _textController, + maxLength: 10, + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + decoration: InputDecoration( + counterText: '', + label: _label != null ? Text(_label!) : null, + errorStyle: TextStyle(height: 0), + errorBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).errorColor, width: 2.0), + ), + suffix: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + InkWell( + onTap: () { + final onComplete = _onComplete; + if (onComplete != null) { + onComplete(_dateTime); + } + }, + child: Icon(Icons.check_circle_outline, color: Theme.of(context).colorScheme.primary), + ), + SizedBox(width: _suffixPadding * 0.5), + InkWell( + onTap: () { + _dateTime = null; + _textController.clear(); + final onComplete = _onComplete; + if (onComplete != null) { + onComplete(null); + } + }, + child: Icon(Icons.cancel_outlined, color: Theme.of(context).colorScheme.primary), + ), + ], + ), + + ), + inputFormatters: [ + DateInputFormatter(separator: '.'), + ], + onChanged: (value) { + _dateTime = _parseDate(_textController.text); + final onChanged = _onChanged; + if (onChanged != null) { + onChanged(_dateTime); + } + }, + onEditingComplete: () { + setState(() { + _autovalidateMode = AutovalidateMode.always; + }); + final onComplete = _onComplete; + if (onComplete != null) { + onComplete(_dateTime); + } + }, + onFieldSubmitted: (value) { + setState(() { + _autovalidateMode = AutovalidateMode.always; + }); + final onSubmitted = _onSubmitted; + if (onSubmitted != null) { + onSubmitted(_dateTime); + } + }, + autovalidateMode: _autovalidateMode, + validator: (value) { + if (value != null && value.isEmpty) { + return null; + } + return _dateTime == null ? '' : null; + }, + ); + } + /// + DateTime? _parseDate(String? value) { + if (value != null) { + return DateTime.tryParse(value.split('.').reversed.join()); + } + return null; + } + /// + @override + void dispose() { + _textController.dispose(); + super.dispose(); + } +} + +/// +class DateInputFormatter extends TextInputFormatter { + final String _separator; + /// + DateInputFormatter({ + String separator = '.', + }) : + _separator = separator; + @override + TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { + return newValue.copyWith( + text: _addSeperators(newValue.text, _separator), + selection: updateCursorPosition(oldValue, newValue), + ); + } + /// + String _addSeperators(String value, String seperator) { + value = value.replaceAll(RegExp(r"\D"), ''); + var newString = ''; + for (int i = 0; i < value.length; i++) { + newString += value[i]; + if (i == 1) { + newString += seperator; + } + if (i == 3) { + newString += seperator; + } + } + return newString; + } + /// + TextSelection updateCursorPosition(TextEditingValue oldValue, TextEditingValue newValue) { + if (oldValue.text.length == 1 && newValue.text.length == 2) { + return TextSelection.fromPosition(TextPosition(offset: 3)); + } else if (oldValue.text.length == 4 && newValue.text.length == 5) { + return TextSelection.fromPosition(TextPosition(offset: 6)); + } + return newValue.selection; + } +} \ No newline at end of file From 05f9fe037c143285ed6dbd270bdf1fef4436bbc5 Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 1 Feb 2023 18:46:27 +0300 Subject: [PATCH 04/25] Adding edit fields (WIP) --- .gitignore | 2 + lib/src/dialogs/auth_dialog.dart | 238 ++++++++++++ .../date_edit_field.dart | 0 .../network_edit_field.dart | 363 ++++++++++++++++++ .../network_dropdown_field.dart | 273 +++++++++++++ .../network_dropdown_field/oil_data.dart | 48 +++ .../network_field_authenticate.dart | 30 ++ pubspec.yaml | 5 +- 8 files changed, 957 insertions(+), 2 deletions(-) create mode 100644 lib/src/dialogs/auth_dialog.dart rename lib/src/edit_field/{ => date_edit_field}/date_edit_field.dart (100%) create mode 100644 lib/src/edit_field/netword_edit_field/network_edit_field.dart create mode 100644 lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart create mode 100644 lib/src/edit_field/network_dropdown_field/oil_data.dart create mode 100644 lib/src/edit_field/network_field_authenticate.dart diff --git a/.gitignore b/.gitignore index 73a16e4..9ee7c82 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ migrate_working_dir/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/.flutter-plugins +/.flutter-plugins-dependencies /pubspec.lock **/doc/api/ .dart_tool/ diff --git a/lib/src/dialogs/auth_dialog.dart b/lib/src/dialogs/auth_dialog.dart new file mode 100644 index 0000000..17d7d13 --- /dev/null +++ b/lib/src/dialogs/auth_dialog.dart @@ -0,0 +1,238 @@ +import 'package:another_flushbar/flushbar_helper.dart'; +import 'package:flutter/material.dart'; +import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_networking/hmi_networking.dart'; +/// +class AuthDialog extends StatefulWidget { + final AppUserSingle? _currentUser; + final String _passwordKey; + /// + const AuthDialog({ + Key? key, + AppUserSingle? currentUser, + required String passwordKey, + }) : + _currentUser = currentUser, + _passwordKey = passwordKey, + super(key: key); + /// + @override + // ignore: no_logic_in_create_state + State createState() => _AuthDialogState( + currentUser: _currentUser, + passwordKey: _passwordKey, + ); +} + +/// +class _AuthDialogState extends State { + static const _debug = true; + final AppUserSingle? _currentUser; + final String _passwordKey; + late Authenticate _auth; + late UserLogin _userLogin; + late UserPassword _userPass; + _AuthDialogState({ + AppUserSingle? currentUser, + required String passwordKey, + }) : + _currentUser = currentUser, + _passwordKey = passwordKey; + + /// + @override + void initState() { + _userLogin = const UserLogin(value: ''); + _userPass = UserPassword(value: '', key: _passwordKey); + _auth = Authenticate( + user: AppUserSingle( + remote: dataSource.dataSet('app-user'), + ), + passwordKey: _passwordKey, + authMessages: AuthMessages(), + ); + super.initState(); + } + /// + @override + Widget build(BuildContext context) { + log(_debug, '[_AuthDialogState.build]'); + const paddingValue = 13.0; + return Scaffold( + // ignoring: true, + body: Center( + child: SizedBox( + width: 600, + // height: 400, + child: Form( + autovalidateMode: AutovalidateMode.always, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + // padding: const EdgeInsets.all(paddingValue * 2), + children: [ + Text( + const AppText('Please authenticate to continue...').local, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: paddingValue), + SizedBox( + width: 600, + // height: 400, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RepaintBoundary( + child: TextFormField( + style: Theme.of(context).textTheme.bodyMedium, + keyboardType: TextInputType.number, + maxLength: 254, + decoration: InputDecoration( + prefixIcon: const Icon( + Icons.account_circle, + ), + prefixStyle: Theme.of(context).textTheme.bodyMedium, + labelText: const AppText('Your login').local, + labelStyle: Theme.of(context).textTheme.bodyMedium, + errorMaxLines: 3, + ), + autocorrect: false, + initialValue: _userLogin.value(), + validator: (value) => _userLogin.validate().message(), + onChanged: (phone) { + setState(() { + _userLogin = UserLogin(value: phone); + }); + }, + ), + ), + const SizedBox(height: paddingValue), + RepaintBoundary( + child: TextFormField( + style: Theme.of(context).textTheme.bodyMedium, + maxLength: _userPass.maxLength, + decoration: const InputDecoration( + prefixIcon: Icon( + Icons.lock, + // color: appThemeData.colorScheme.onPrimary, + ), + errorStyle: TextStyle( + height: 1.1, + ), + errorMaxLines: 5, + ), + autocorrect: false, + onChanged: (value) { + setState(() { + _userPass = UserPassword(value: value); + }); + }, + ), + ), + ], + ), + ), + const SizedBox(height: paddingValue), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton( + onPressed: () { + _onComplete(context, true); + }, + child: Text( + const AppText('Cancel').local, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + const SizedBox(width: paddingValue), + ElevatedButton( + onPressed: () { + _onComplete(context, false); + }, + child: Text( + const AppText('Next').local, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + /// + void _onComplete(BuildContext context, bool cancel) { + log(_debug, '[_AuthDialogState._onComplete] cancel: $cancel'); + if (cancel) { + Navigator.of(context).pop( + AuthResult( + authenticated: false, + message: const AppText('Canceled by user').local, + user: AppUserSingle(remote: DataSet.empty()), + ), + ); + } else { + final currentUser = _currentUser; + if ((currentUser != null) && (_userLogin.value() == '${currentUser['login']}')) { + FlushbarHelper.createError( + duration: AppUiSettings.flushBarDurationMedium, + title: const AppText('Authentication').local, + message: const AppText('User already authenticated').local, + ).show(context); + return ; + } + if (_userLogin.validate().valid() && _userPass.validate().valid()) { + _auth.authenticateByLoginAndPass(_userLogin.value(), _userPass.value()) + .then((authResult) { + if (authResult.authenticated) { + FlushbarHelper.createSuccess( + duration: AppUiSettings.flushBarDurationMedium, + title: const AppText('Authentication').local, + message: authResult.message, + ).show(context); + Future.delayed(AppUiSettings.flushBarDurationMedium) + .then((_) { + Navigator.of(context).pop(authResult); + }); + } else { + FlushbarHelper.createError( + duration: AppUiSettings.flushBarDurationMedium, + title: const AppText('Authentication').local, + message: authResult.message, + ).show(context); + } + }); + } else { + final message = _buildWrongLoginPassMessage(); + log(_debug, '[_AuthDialogState._onComplete] message: $message'); + FlushbarHelper.createError( + duration: AppUiSettings.flushBarDurationMedium, + title: const AppText('Authentication').local, + message: message, + ).show(context); + } + } + } + /// + /// Builds message on wrong login or password + String _buildWrongLoginPassMessage() { + final wrongLoginMessage = _userLogin.validate().valid() + ? '' + : const AppText('Wrong login').local; + final wrongPassMessage = _userPass.validate().valid() + ? '' + : const AppText('Wrong password').local; + final andText = (wrongLoginMessage.isNotEmpty && wrongPassMessage.isNotEmpty) + ? ' ${const AppText('and').local} ' + : ''; + return '$wrongLoginMessage$andText$wrongPassMessage'; + } +} diff --git a/lib/src/edit_field/date_edit_field.dart b/lib/src/edit_field/date_edit_field/date_edit_field.dart similarity index 100% rename from lib/src/edit_field/date_edit_field.dart rename to lib/src/edit_field/date_edit_field/date_edit_field.dart diff --git a/lib/src/edit_field/netword_edit_field/network_edit_field.dart b/lib/src/edit_field/netword_edit_field/network_edit_field.dart new file mode 100644 index 0000000..3deef30 --- /dev/null +++ b/lib/src/edit_field/netword_edit_field/network_edit_field.dart @@ -0,0 +1,363 @@ +import 'package:another_flushbar/flushbar_helper.dart'; +import 'package:hmi_networking/hmi_networking.dart'; +import 'package:flutter/material.dart'; +import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_widgets/src/edit_field/network_field_authenticate.dart'; +import 'package:hmi_widgets/src/theme/app_theme.dart'; + +/// +/// Gets and shows the value of type [T] from the DataServer. +/// If the value edited by user, sends new value to the DataServer. +/// The value can be edited onle if current user present in the list of allwed. +/// Shows progress indicator until network operation complited. +class NetworkEditField extends StatefulWidget { + final DsClient? _dsClient; + final DsPointName? _writeTagName; + final String? _responseTagName; + final AppUserStacked? _users; + final List _allowedGroups; + final TextInputType? _keyboardType; + final int _fractionDigits; + final String? _labelText; + final String? _unitText; + final double _width; + final bool _showApplyButton; + final String _notPermittedMessage; + final Duration _flushBarDuration; + /// + /// - [writeTagName] - the name of DataServer tag to send value + /// - [responseTagName] - the name of DataServer tag to get response if value written + /// - [users] - current stack of authenticated users + /// tried to edit the value but not in list of allowed + /// - [allowedGroups] - list of user group names allowed to edit this field + const NetworkEditField({ + Key? key, + List allowedGroups = const [], + AppUserStacked? users, + DsClient? dsClient, + DsPointName? writeTagName, + String? responseTagName, + TextInputType? keyboardType, + int fractionDigits = 0, + String? labelText, + String? unitText, + double width = 230.0, + showApplyButton = false, + String notPermittedMessage = 'Editing is not permitted for current user', + Duration flushBarDuration = const Duration(milliseconds: 1000), + }) : + _allowedGroups = allowedGroups, + _users = users, + _dsClient = dsClient, + _writeTagName = writeTagName, + _responseTagName = responseTagName, + _keyboardType = keyboardType, + _fractionDigits = fractionDigits, + _labelText = labelText, + _unitText = unitText, + _width = width, + _showApplyButton = showApplyButton, + _notPermittedMessage = notPermittedMessage, + _flushBarDuration = flushBarDuration, + super(key: key); + /// + @override + // ignore: no_logic_in_create_state + State> createState() => _NetworkEditFieldState( + users: _users, + dsClient: _dsClient, + writeTagName: _writeTagName, + responseTagName: _responseTagName, + allowedGroups: _allowedGroups, + keyboardType: _keyboardType, + fractionDigits: _fractionDigits, + labelText: _labelText, + unitText: _unitText, + width: _width, + showApplyButton: _showApplyButton, + notPermittedMessage: _notPermittedMessage, + flushBarDuration: _flushBarDuration, + ); +} + +/// +class _NetworkEditFieldState extends State> { + static const _debug = true; + final _state = NetworkOperationState(isLoading: true); + final TextEditingController _editingController = TextEditingController(); + final List _allowedGroups; + late AppUserStacked? _users; + final DsClient? _dsClient; + final DsPointName? _writeTagName; + final String? _responseTagName; + final TextInputType? _keyboardType; + final int _fractionDigits; + final String? _labelText; + final String? _unitText; + final double _width; + final bool _showApplyButton; + final String _notPermittedMessage; + final Duration _flushBarDuration; + // bool _accessAllowed = false; + String _initValue = ''; + /// + _NetworkEditFieldState({ + required List allowedGroups, + required AppUserStacked? users, + required DsClient? dsClient, + required DsPointName? writeTagName, + required String? responseTagName, + required TextInputType? keyboardType, + required int fractionDigits, + required String? labelText, + required String? unitText, + required double width, + required bool showApplyButton, + required String notPermittedMessage, + required Duration flushBarDuration, + }) : + assert(T == int || T == double, 'Generic must be int or double.'), + _allowedGroups = allowedGroups, + _users = users, + _dsClient = dsClient, + _writeTagName = writeTagName, + _responseTagName = responseTagName, + _keyboardType = keyboardType, + _fractionDigits = fractionDigits, + _labelText = labelText, + _unitText = unitText, + _width = width, + _showApplyButton = showApplyButton, + _notPermittedMessage = notPermittedMessage, + _flushBarDuration = flushBarDuration, + super(); + /// + @override + void initState() { + super.initState(); + } + /// + @override + void didChangeDependencies() { + // final themeData = Theme.of(context); + final statusColors = Theme.of(context).stateColors; + // _editingController = TextEditingController(text: _newValue); + final dsClient = _dsClient; + final writeTagName = _writeTagName; + final responseTagName = _responseTagName != null + ? _responseTagName + : writeTagName != null + ? writeTagName.name + : writeTagName != null + ? writeTagName.name + : null; + if (dsClient != null) { + DsDataStreamExtract( + stream: (responseTagName != null) + ? dsClient.stream(responseTagName) + : null, + stateColors: statusColors, + ).stream.listen((event) { + log(_debug, '[$runtimeType.didChangeDependencies] event: $event'); + log(_debug, '[$runtimeType.didChangeDependencies] event.value: ${event.value}'); + _initValue = (event.value as num).toStringAsFixed(_fractionDigits); + if (!_state.isEditing) { + log(_debug, '[$runtimeType.didChangeDependencies] _initValue: $_initValue'); + // setState(() { + _editingController.text = _initValue; + // }); + } + if (mounted) { + setState(() { + _state.setLoaded(); + }); + } + }); + } + super.didChangeDependencies(); + } + /// + @override + Widget build(BuildContext context) { + log(_debug, '[$_NetworkEditFieldState.build] _users', _users?.toList()); + return SizedBox( + width: _width, + child: RepaintBoundary( + child: TextFormField( + controller: _editingController, + keyboardType: _keyboardType, + textAlign: TextAlign.end, + decoration: InputDecoration( + suffixText: _unitText, + prefixStyle: Theme.of(context).textTheme.bodyMedium, + label: Text( + '$_labelText', + softWrap: false, + overflow: TextOverflow.fade, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontSize: Theme.of(context).textTheme.labelLarge!.fontSize, + ), + ), + alignLabelWithHint: true, + errorMaxLines: 3, + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (_showApplyButton) + IconButton( + onPressed: () => _onEditingComplete(), + icon: Icon(Icons.check_circle_outline, color: Theme.of(context).colorScheme.primary), + ), + _buildSufixIcon(), + ], + ), + filled: true, + fillColor: Theme.of(context).backgroundColor, + ), + onChanged: (newValue) async { + log(_debug, '[$_NetworkEditFieldState.build.onChanged] newValue: $newValue'); + if (_state.isAuthenticating) { + _editingController.text = _initValue; + } else { + if (newValue != _initValue) { + await _requestAccess().then((_) { + if (_state.isAuthenticeted) { + if (newValue == _initValue) { + _state.setLoaded(); + } else if (!_state.isChanged) { + _state.setEditing(); + _state.setChanged(); + } + if (mounted) setState(() {;}); + } else { + _editingController.text = _initValue; + } + }); + } + } + }, + onEditingComplete: () => _onEditingComplete(), + onFieldSubmitted: (value) { + log(_debug, '[$_NetworkEditFieldState.build] onFieldSubmitted'); + }, + onSaved: (newValue) { + log(_debug, '[$_NetworkEditFieldState.build] onSaved'); + }, + ), + ), + ); + } + /// + void _onEditingComplete() { + log(_debug, '[$_NetworkEditFieldState._onEditingComplete]'); + T? numValue; + if (T == int) { + numValue = int.tryParse(_editingController.text) as T; + } + if (T == double) { + numValue = _textToFixedDouble(_editingController.text, _fractionDigits) as T; + } + log(_debug, '[$_NetworkEditFieldState.build.onEditingComplete] numValue: $numValue\t_initValue: $_initValue'); + if (numValue != double.parse(_initValue)) { + _sendValue(_dsClient, _writeTagName, _responseTagName, numValue); + } else { + _editingController.text = _initValue; + setState(() => _state.setLoaded()); + } + } + /// + double _textToFixedDouble(String value, int fractionDigits) { + final doubleValue = double.tryParse(_editingController.text); + if (doubleValue != null) { + return double.parse(doubleValue.toStringAsFixed(fractionDigits)); + } else { + return 0.0; + } + } + /// + void _sendValue( + DsClient? dsClient, + DsPointName? writeTagName, + String? responseTagName, + T? newValue, + ) { + log(_debug, '[$_NetworkEditFieldState._sendValue] newValue: ', newValue); + final value = newValue; + if (dsClient != null && writeTagName != null && value != null) { + setState(() { + _state.setSaving(); + }); + DsSend( + dsClient: dsClient, + pointName: writeTagName, + response: responseTagName, + ).exec(value).then((responseValue) { + setState(() => _state.setSaved()); + }); + } + } + /// + Widget _buildSufixIcon() { + if (_state.isLoading || _state.isSaving) { + return _buildProgressIndicator(); + } + if (_state.isChanged) { + return const Icon(Icons.info_outline); + } + if (_state.isSaved) { + return Icon(Icons.check, color: Theme.of(context).primaryColor); + } + return Icon(null); + } + /// + Widget _buildProgressIndicator() { + return const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 3, + ), + ); + } + /// Проверяет наличие доступа у текущего пользователя + /// на редактирования данного поля + Future _requestAccess() async { + _state.setAuthenticating(); + if (_allowedGroups.isEmpty) { + _state.setAuthenticated(); + // _accessAllowed = true; + return; + } + final users = _users; + if (users != null) { + log(_debug, '[$_NetworkEditFieldState._requestAccess] users:', users.toList()); + final user = users.peek; + log(_debug, '[$_NetworkEditFieldState._requestAccess] user:', user); + log(_debug, '[$_NetworkEditFieldState._requestAccess] _user.group:', user.userGroup().value); + if (user.exists()) { + if (_allowedGroups.contains(user.userGroup().value)) { + _state.setAuthenticated(); + // _accessAllowed = true; + return; + } + } + networkFieldAuthenticate(context, users).then((AuthResult authResult) { + if (authResult.authenticated) { + setState(() { + _state.setAuthenticated(); + // _accessAllowed = true; + return; + }); + } + }); + } + FlushbarHelper.createError( + duration: _flushBarDuration, + message: _notPermittedMessage, + ).show(context); + _state.setAuthenticated(authenticated: false); + // _accessAllowed = false; + } +} diff --git a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart new file mode 100644 index 0000000..20c5afe --- /dev/null +++ b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart @@ -0,0 +1,273 @@ +import 'dart:core'; +import 'package:hmi_networking/hmi_networking.dart'; +import 'package:flutter/material.dart'; +import 'package:hmi_core/hmi_core.dart'; +import 'package:another_flushbar/flushbar_helper.dart'; +import 'package:hmi_widgets/src/edit_field/network_field_authenticate.dart'; +import 'package:hmi_widgets/src/theme/app_theme.dart'; +import 'oil_data.dart'; + +/// +class NetworkDropdownFormField extends StatefulWidget { + // final Future Function(BuildContext context)? _onAuthRequested; + final List _allowedGroups; + final AppUserStacked? _users; + final DsClient? _dsClient; + final DsPointName? _writeTagName; + final String? _responseTagName; + final String? _labelText; + final double _width; + final String _notPermittedMessage; + final Duration _flushBarDuration; + /// + const NetworkDropdownFormField({ + Key? key, + // Future Function(BuildContext context)? onAuthRequested, + List allowedGroups = const [], + AppUserStacked? users, + DsClient? dsClient, + DsPointName? writeTagName, + String? responseTagName, + String? labelText, + double width = 350.0, + String notPermittedMessage = 'Editing is not permitted for current user', + Duration flushBarDuration = const Duration(milliseconds: 1000), + }) : + // _onAuthRequested = onAuthRequested, + _allowedGroups = allowedGroups, + _users = users, + _dsClient = dsClient, + _writeTagName = writeTagName, + _responseTagName = responseTagName, + _labelText = labelText, + _width = width, + _notPermittedMessage = notPermittedMessage, + _flushBarDuration = flushBarDuration, + super(key: key); + /// + @override + State createState() => _NetworkDropdownFormFieldState( + // onAuthRequested: _onAuthRequested, + allowedGroups: _allowedGroups, + users: _users, + dsClient: _dsClient, + writeTagName: _writeTagName, + responseTagName: _responseTagName, + labelText: _labelText, + width: _width, + notPermittedMessage: _notPermittedMessage, + flushBarDuration: _flushBarDuration, + ); +} +/// +class _NetworkDropdownFormFieldState extends State { + static const _debug = true; + final dropdownState = GlobalKey(); + final _state = NetworkOperationState(isLoading: true); + final OilData _oilData = OilData(assetName: 'assets/settings/oil-list.json'); + // final Future Function(BuildContext context)? _onAuthRequested; + final List _allowedGroups; + late AppUserStacked? _users; + final DsClient? _dsClient; + final DsPointName? _writeTagName; + final String? _responseTagName; + final String? _labelText; + final double _width; + final String _notPermittedMessage; + final Duration _flushBarDuration; + bool _accessAllowed = false; + int? _dropdownValue; + int? _initValue; + List _oilNames = []; + /// + _NetworkDropdownFormFieldState({ + // required Future Function(BuildContext context)? onAuthRequested, + required List allowedGroups, + required AppUserStacked? users, + required DsClient? dsClient, + required DsPointName? writeTagName, + required String? responseTagName, + required String? labelText, + required double width, + required String notPermittedMessage, + required Duration flushBarDuration, + }) : + // _onAuthRequested = onAuthRequested, + _allowedGroups = allowedGroups, + _users = users, + _dsClient = dsClient, + _writeTagName = writeTagName, + _responseTagName = responseTagName, + _labelText = labelText, + _width = width, + _flushBarDuration = flushBarDuration, + _notPermittedMessage = notPermittedMessage, + super(); + /// + @override + void initState() { + super.initState(); + _oilData.names() + .then((value) { + setState(() { + _oilNames = value; + }); + }); + } + /// + @override + Widget build(BuildContext context) { + final width = _width; + final _dropMenuItemWidth = width * 0.7; + final stateColors = Theme.of(context).stateColors; + final dsClient = _dsClient; + final responseTagName = _buildResponseTagName(_responseTagName, _writeTagName); + return SizedBox( + width: _width, + child: StreamBuilder>( + stream: DsDataStreamExtract( + stream: (dsClient != null && responseTagName != null) + ? dsClient.streamInt(responseTagName) + : null, + stateColors: stateColors, + ).stream, + builder: (context, snapshot) { + if (snapshot.hasError) { + // TODO add implementation or remove this block + } else if (snapshot.hasData) { + final point = snapshot.data; + if (point != null) { + _initValue = point.value; + _dropdownValue = _initValue; + _state.setLoaded(); + } + } + return DropdownButtonFormField( + key: dropdownState, + value: _dropdownValue, + onChanged: (newValue) { + log(_debug, '[$_NetworkDropdownFormFieldState.onChanged] value: $newValue'); + if (newValue != _initValue) { + dropdownState.currentState?.didChange(_initValue); + } + _requestAccess().then((value) { + if (_accessAllowed) { + _sendValue(_dsClient, _writeTagName, _responseTagName, newValue); + } + }); + }, + decoration: InputDecoration( + labelText: _labelText, + labelStyle: Theme.of(context).textTheme.bodySmall!.copyWith( + fontSize: Theme.of(context).textTheme.labelLarge!.fontSize, + ), + suffixIcon: _buildSufixIcon(), + filled: true, + fillColor: Theme.of(context).backgroundColor, + ), + alignment: AlignmentDirectional.centerEnd, + items: _buildDropdownMenuItems(context, _oilNames, _dropMenuItemWidth), + ); + }, + ), + ); + } + /// + void _sendValue(DsClient? dsClient, DsPointName? writeTagName, String? responseTagName, int? newValue) { + final value = newValue; + if (dsClient != null && writeTagName != null && value != null) { + setState(() { + _state.setSaving(); + }); + DsSend( + dsClient: dsClient, + pointName: writeTagName, + response: responseTagName, + ) + .exec(value) + .then((responseValue) { + setState(() { + _state.setSaved(); + }); + }); + } + } + /// + List> _buildDropdownMenuItems(BuildContext context, List oilNames, double width) { + return oilNames.asMap().map((index, name) { + log(_debug, '[$_NetworkDropdownFormFieldState._buildDropdownMenuItems]'); + return MapEntry( + index, + DropdownMenuItem( + value: index, + child: SizedBox(width: width, child: Text(name, textAlign: TextAlign.center)), + alignment: Alignment.center, + ), + ); + }).values.toList(); + } + /// + String? _buildResponseTagName(String? responseTagName, DsPointName? writeTagName) { + if (responseTagName != null) { + return responseTagName; + } + if (writeTagName != null) { + return writeTagName.name; + } + return null; + } + /// + Widget? _buildSufixIcon() { + if (_state.isLoading || _state.isSaving) { + return _buildProgressIndicator(); + } + if (_state.isChanged) { + return const Icon(Icons.info_outline); + } + if (_state.isSaved) { + return Icon(Icons.check, color: Theme.of(context).primaryColor); + } + return null; + } + /// + Widget _buildProgressIndicator() { + return const SizedBox( + width: 13, height: 13, + child: Padding( + padding: EdgeInsets.all(9.0), + child: CircularProgressIndicator(strokeWidth: 3,), + ), + ); + } + /// Проверяет наличие доступа у текущего пользователя + /// на редактирования данного поля + Future _requestAccess() async { + if (_allowedGroups.isEmpty) { + _accessAllowed = true; + return; + } + final users = _users; + if (users != null) { + final user = users.peek; + if (user.exists()) { + if (_allowedGroups.contains(user.userGroup().value)) { + _accessAllowed = true; + return; + } + } + networkFieldAuthenticate(context, users).then((AuthResult authResult) { + if (authResult.authenticated) { + setState(() { + _accessAllowed = true; + return; + }); + } + }); + } + FlushbarHelper.createError( + duration: _flushBarDuration, + message: _notPermittedMessage, + ).show(context); + _accessAllowed = false; + } +} diff --git a/lib/src/edit_field/network_dropdown_field/oil_data.dart b/lib/src/edit_field/network_dropdown_field/oil_data.dart new file mode 100644 index 0000000..070289e --- /dev/null +++ b/lib/src/edit_field/network_dropdown_field/oil_data.dart @@ -0,0 +1,48 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:hmi_core/hmi_core.dart'; + +/// +/// Reads oil names and spec data for [SettingsPage] - HPU +/// from assets json file +class OilData { + static const _debug = true; + final String _assetName; + final Map> _data = {}; + /// + OilData({ + required String assetName, + }) : + _assetName = assetName; + /// + /// List of names of awailable oil types + Future> names() async { + if (_data.isEmpty) { + await _loadAsset('$_assetName') + .then((value) => _data.addAll(value)); + } + final namesList = _data.keys.toList(); + log(_debug, '[$OilData.names] namesList: ', namesList); + return Future.value(namesList); + } + /// + Future>> _loadAsset(String assetName) { + return rootBundle.loadString(assetName) + .then((value) { + final jsonResult = json.decode(value) as Map; + return jsonResult.map((key, value) { + return MapEntry( + key, + value as Map, + ); + }); + }) + .onError((error, stackTrace) { + throw Failure.unexpected( + message: 'Ошибка в методе _loadAsset класса $runtimeType:\n$error', + stackTrace: stackTrace, + ); + }); + } +} \ No newline at end of file diff --git a/lib/src/edit_field/network_field_authenticate.dart b/lib/src/edit_field/network_field_authenticate.dart new file mode 100644 index 0000000..f3e671e --- /dev/null +++ b/lib/src/edit_field/network_field_authenticate.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_networking/hmi_networking.dart'; +import 'package:hmi_widgets/src/dialogs/auth_dialog.dart'; +/// + Future networkFieldAuthenticate(BuildContext context, AppUserStacked users) { + const _debug = true; + return Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => AuthDialog( + key: UniqueKey(), + currentUser: users.peek, + ), + settings: const RouteSettings(name: "/authDialog"), + ), + ).then((authResult) { + log(_debug, '[_authenticate] authResult: ', authResult); + final result = authResult; + if (result != null) { + if (result.authenticated) { + users.push(result.user); + } + return result; + } + throw Failure.unexpected( + message: 'Authentication error, null returned instead of AuthResult ', + stackTrace: StackTrace.current, + ); + }); + } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index d9a1097..ad9bc3f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,16 +16,17 @@ dev_dependencies: sdk: flutter lint: ^1.10.0 mockito: ^5.3.0 + another_flushbar: ^1.12.29 # flutter_lints: ^2.0.0 fl_chart: ^0.55.1 hmi_core: git: url: https://github.com/a-givertzman/hmi_core.git - ref: master + ref: Move-to-hmi_core hmi_networking: git: url: https://github.com/a-givertzman/hmi_networking.git - ref: master + ref: Add-to-hmi_networking # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 63407772ef4cbdbf43a0f253e8bb3135a882b3ec Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 1 Feb 2023 18:47:57 +0300 Subject: [PATCH 05/25] Added exports --- lib/hmi_widgets.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/hmi_widgets.dart b/lib/hmi_widgets.dart index d160729..af9383a 100644 --- a/lib/hmi_widgets.dart +++ b/lib/hmi_widgets.dart @@ -45,4 +45,8 @@ export 'src/charts/live_chart/live_chart_widget.dart'; export 'src/process/electrical/drive/ac_drive_widget.dart'; // // Edit fields -export 'src/edit_field/date_edit_field.dart'; \ No newline at end of file +export 'src/edit_field/date_edit_field/date_edit_field.dart'; +export 'src/edit_field/netword_edit_field/network_edit_field.dart'; +export 'src/edit_field/network_dropdown_field/network_dropdown_field.dart'; +export 'src/edit_field/network_dropdown_field/oil_data.dart'; +export 'src/edit_field/network_field_authenticate.dart'; \ No newline at end of file From 046aa4b8d726434ee12c74abfe44aa22f081866d Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Thu, 2 Feb 2023 12:46:26 +0300 Subject: [PATCH 06/25] Passed objects for auth to network edit fields --- lib/src/dialogs/auth_dialog.dart | 66 ++++++++++++------- .../network_edit_field.dart | 39 ++++++++--- .../network_dropdown_field.dart | 48 +++++++++++--- .../network_field_authenticate.dart | 15 ++++- .../linear_value_indicator.dart | 1 - 5 files changed, 127 insertions(+), 42 deletions(-) diff --git a/lib/src/dialogs/auth_dialog.dart b/lib/src/dialogs/auth_dialog.dart index 17d7d13..b2162cd 100644 --- a/lib/src/dialogs/auth_dialog.dart +++ b/lib/src/dialogs/auth_dialog.dart @@ -1,19 +1,29 @@ import 'package:another_flushbar/flushbar_helper.dart'; import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_core/hmi_core_translate.dart' as translate; import 'package:hmi_networking/hmi_networking.dart'; /// class AuthDialog extends StatefulWidget { final AppUserSingle? _currentUser; final String _passwordKey; + final translate.Localizations _localizations; + final Duration _flushBarDuration; + final DataSource _dataSource; /// const AuthDialog({ Key? key, AppUserSingle? currentUser, required String passwordKey, + required translate.Localizations localizations, + Duration flushBarDuration = const Duration(milliseconds: 1000), + required DataSource dataSource, }) : _currentUser = currentUser, _passwordKey = passwordKey, + _localizations = localizations, + _flushBarDuration = flushBarDuration, + _dataSource = dataSource, super(key: key); /// @override @@ -21,6 +31,9 @@ class AuthDialog extends StatefulWidget { State createState() => _AuthDialogState( currentUser: _currentUser, passwordKey: _passwordKey, + localizations: _localizations, + flushbarDuration: _flushBarDuration, + dataSource: _dataSource, ); } @@ -29,15 +42,24 @@ class _AuthDialogState extends State { static const _debug = true; final AppUserSingle? _currentUser; final String _passwordKey; + final translate.Localizations _localizations; + final Duration _flushBarDuration; + final DataSource _dataSource; late Authenticate _auth; late UserLogin _userLogin; late UserPassword _userPass; _AuthDialogState({ AppUserSingle? currentUser, required String passwordKey, + required translate.Localizations localizations, + Duration flushbarDuration = const Duration(milliseconds: 1000), + required DataSource dataSource, }) : _currentUser = currentUser, - _passwordKey = passwordKey; + _passwordKey = passwordKey, + _localizations = localizations, + _flushBarDuration = flushbarDuration, + _dataSource = dataSource; /// @override @@ -46,10 +68,10 @@ class _AuthDialogState extends State { _userPass = UserPassword(value: '', key: _passwordKey); _auth = Authenticate( user: AppUserSingle( - remote: dataSource.dataSet('app-user'), + remote: _dataSource.dataSet('app-user'), ), passwordKey: _passwordKey, - authMessages: AuthMessages(), + localizations: _localizations, ); super.initState(); } @@ -72,7 +94,7 @@ class _AuthDialogState extends State { // padding: const EdgeInsets.all(paddingValue * 2), children: [ Text( - const AppText('Please authenticate to continue...').local, + _localizations.tr('Please authenticate to continue...'), style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: paddingValue), @@ -92,7 +114,7 @@ class _AuthDialogState extends State { Icons.account_circle, ), prefixStyle: Theme.of(context).textTheme.bodyMedium, - labelText: const AppText('Your login').local, + labelText: _localizations.tr('Your login'), labelStyle: Theme.of(context).textTheme.bodyMedium, errorMaxLines: 3, ), @@ -124,7 +146,7 @@ class _AuthDialogState extends State { autocorrect: false, onChanged: (value) { setState(() { - _userPass = UserPassword(value: value); + _userPass = UserPassword(value: value, key: _passwordKey); }); }, ), @@ -141,7 +163,7 @@ class _AuthDialogState extends State { _onComplete(context, true); }, child: Text( - const AppText('Cancel').local, + _localizations.tr('Cancel'), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), @@ -153,7 +175,7 @@ class _AuthDialogState extends State { _onComplete(context, false); }, child: Text( - const AppText('Next').local, + _localizations.tr('Next'), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), @@ -175,7 +197,7 @@ class _AuthDialogState extends State { Navigator.of(context).pop( AuthResult( authenticated: false, - message: const AppText('Canceled by user').local, + message: _localizations.tr('Canceled by user'), user: AppUserSingle(remote: DataSet.empty()), ), ); @@ -183,9 +205,9 @@ class _AuthDialogState extends State { final currentUser = _currentUser; if ((currentUser != null) && (_userLogin.value() == '${currentUser['login']}')) { FlushbarHelper.createError( - duration: AppUiSettings.flushBarDurationMedium, - title: const AppText('Authentication').local, - message: const AppText('User already authenticated').local, + duration: _flushBarDuration, + title: _localizations.tr('Authentication'), + message: _localizations.tr('User already authenticated'), ).show(context); return ; } @@ -194,18 +216,18 @@ class _AuthDialogState extends State { .then((authResult) { if (authResult.authenticated) { FlushbarHelper.createSuccess( - duration: AppUiSettings.flushBarDurationMedium, - title: const AppText('Authentication').local, + duration: _flushBarDuration, + title: _localizations.tr('Authentication'), message: authResult.message, ).show(context); - Future.delayed(AppUiSettings.flushBarDurationMedium) + Future.delayed(_flushBarDuration) .then((_) { Navigator.of(context).pop(authResult); }); } else { FlushbarHelper.createError( - duration: AppUiSettings.flushBarDurationMedium, - title: const AppText('Authentication').local, + duration: _flushBarDuration, + title: _localizations.tr('Authentication'), message: authResult.message, ).show(context); } @@ -214,8 +236,8 @@ class _AuthDialogState extends State { final message = _buildWrongLoginPassMessage(); log(_debug, '[_AuthDialogState._onComplete] message: $message'); FlushbarHelper.createError( - duration: AppUiSettings.flushBarDurationMedium, - title: const AppText('Authentication').local, + duration: _flushBarDuration, + title: _localizations.tr('Authentication'), message: message, ).show(context); } @@ -226,12 +248,12 @@ class _AuthDialogState extends State { String _buildWrongLoginPassMessage() { final wrongLoginMessage = _userLogin.validate().valid() ? '' - : const AppText('Wrong login').local; + : _localizations.tr('Wrong login'); final wrongPassMessage = _userPass.validate().valid() ? '' - : const AppText('Wrong password').local; + : _localizations.tr('Wrong password'); final andText = (wrongLoginMessage.isNotEmpty && wrongPassMessage.isNotEmpty) - ? ' ${const AppText('and').local} ' + ? ' ${_localizations.tr('and')} ' : ''; return '$wrongLoginMessage$andText$wrongPassMessage'; } diff --git a/lib/src/edit_field/netword_edit_field/network_edit_field.dart b/lib/src/edit_field/netword_edit_field/network_edit_field.dart index 3deef30..93e76e4 100644 --- a/lib/src/edit_field/netword_edit_field/network_edit_field.dart +++ b/lib/src/edit_field/netword_edit_field/network_edit_field.dart @@ -2,6 +2,7 @@ import 'package:another_flushbar/flushbar_helper.dart'; import 'package:hmi_networking/hmi_networking.dart'; import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_core/hmi_core_translate.dart' as translate; import 'package:hmi_widgets/src/edit_field/network_field_authenticate.dart'; import 'package:hmi_widgets/src/theme/app_theme.dart'; @@ -22,8 +23,10 @@ class NetworkEditField extends StatefulWidget { final String? _unitText; final double _width; final bool _showApplyButton; - final String _notPermittedMessage; final Duration _flushBarDuration; + final translate.Localizations _localizations; + final DataSource _dataSource; + final String _passwordKey; /// /// - [writeTagName] - the name of DataServer tag to send value /// - [responseTagName] - the name of DataServer tag to get response if value written @@ -45,6 +48,9 @@ class NetworkEditField extends StatefulWidget { showApplyButton = false, String notPermittedMessage = 'Editing is not permitted for current user', Duration flushBarDuration = const Duration(milliseconds: 1000), + required String passwordKey, + required DataSource dataSource, + required translate.Localizations localizations, }) : _allowedGroups = allowedGroups, _users = users, @@ -57,8 +63,10 @@ class NetworkEditField extends StatefulWidget { _unitText = unitText, _width = width, _showApplyButton = showApplyButton, - _notPermittedMessage = notPermittedMessage, _flushBarDuration = flushBarDuration, + _passwordKey = passwordKey, + _dataSource = dataSource, + _localizations = localizations, super(key: key); /// @override @@ -75,8 +83,10 @@ class NetworkEditField extends StatefulWidget { unitText: _unitText, width: _width, showApplyButton: _showApplyButton, - notPermittedMessage: _notPermittedMessage, flushBarDuration: _flushBarDuration, + localizations: _localizations, + passwordKey: _passwordKey, + dataSource: _dataSource, ); } @@ -96,8 +106,10 @@ class _NetworkEditFieldState extends State> { final String? _unitText; final double _width; final bool _showApplyButton; - final String _notPermittedMessage; final Duration _flushBarDuration; + final DataSource _dataSource; + final String _passwordKey; + final translate.Localizations _localizations; // bool _accessAllowed = false; String _initValue = ''; /// @@ -113,8 +125,10 @@ class _NetworkEditFieldState extends State> { required String? unitText, required double width, required bool showApplyButton, - required String notPermittedMessage, required Duration flushBarDuration, + required DataSource dataSource, + required String passwordKey, + required translate.Localizations localizations, }) : assert(T == int || T == double, 'Generic must be int or double.'), _allowedGroups = allowedGroups, @@ -128,8 +142,10 @@ class _NetworkEditFieldState extends State> { _unitText = unitText, _width = width, _showApplyButton = showApplyButton, - _notPermittedMessage = notPermittedMessage, _flushBarDuration = flushBarDuration, + _dataSource = dataSource, + _passwordKey = passwordKey, + _localizations = localizations, super(); /// @override @@ -343,7 +359,14 @@ class _NetworkEditFieldState extends State> { return; } } - networkFieldAuthenticate(context, users).then((AuthResult authResult) { + networkFieldAuthenticate( + context, + users, + _passwordKey, + _localizations, + _dataSource, + flushbarDuration: _flushBarDuration, + ).then((AuthResult authResult) { if (authResult.authenticated) { setState(() { _state.setAuthenticated(); @@ -355,7 +378,7 @@ class _NetworkEditFieldState extends State> { } FlushbarHelper.createError( duration: _flushBarDuration, - message: _notPermittedMessage, + message: _localizations.tr('Editing is not permitted for current user'), ).show(context); _state.setAuthenticated(authenticated: false); // _accessAllowed = false; diff --git a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart index 20c5afe..9067156 100644 --- a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart +++ b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart @@ -2,6 +2,7 @@ import 'dart:core'; import 'package:hmi_networking/hmi_networking.dart'; import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_core/hmi_core_translate.dart' as translate; import 'package:another_flushbar/flushbar_helper.dart'; import 'package:hmi_widgets/src/edit_field/network_field_authenticate.dart'; import 'package:hmi_widgets/src/theme/app_theme.dart'; @@ -17,8 +18,11 @@ class NetworkDropdownFormField extends StatefulWidget { final String? _responseTagName; final String? _labelText; final double _width; - final String _notPermittedMessage; final Duration _flushBarDuration; + final translate.Localizations _localizations; + final DataSource _dataSource; + final String _passwordKey; + final OilData _oilData; /// const NetworkDropdownFormField({ Key? key, @@ -30,8 +34,11 @@ class NetworkDropdownFormField extends StatefulWidget { String? responseTagName, String? labelText, double width = 350.0, - String notPermittedMessage = 'Editing is not permitted for current user', Duration flushBarDuration = const Duration(milliseconds: 1000), + required translate.Localizations localizations, + required DataSource dataSource, + required String passwordKey, + required OilData oilData, }) : // _onAuthRequested = onAuthRequested, _allowedGroups = allowedGroups, @@ -41,8 +48,11 @@ class NetworkDropdownFormField extends StatefulWidget { _responseTagName = responseTagName, _labelText = labelText, _width = width, - _notPermittedMessage = notPermittedMessage, _flushBarDuration = flushBarDuration, + _localizations = localizations, + _dataSource = dataSource, + _passwordKey = passwordKey, + _oilData = oilData, super(key: key); /// @override @@ -55,8 +65,11 @@ class NetworkDropdownFormField extends StatefulWidget { responseTagName: _responseTagName, labelText: _labelText, width: _width, - notPermittedMessage: _notPermittedMessage, flushBarDuration: _flushBarDuration, + localizations: _localizations, + dataSource: _dataSource, + passwordKey: _passwordKey, + oilData: _oilData, ); } /// @@ -64,7 +77,7 @@ class _NetworkDropdownFormFieldState extends State { static const _debug = true; final dropdownState = GlobalKey(); final _state = NetworkOperationState(isLoading: true); - final OilData _oilData = OilData(assetName: 'assets/settings/oil-list.json'); + final OilData _oilData; // final Future Function(BuildContext context)? _onAuthRequested; final List _allowedGroups; late AppUserStacked? _users; @@ -73,8 +86,10 @@ class _NetworkDropdownFormFieldState extends State { final String? _responseTagName; final String? _labelText; final double _width; - final String _notPermittedMessage; final Duration _flushBarDuration; + final translate.Localizations _localizations; + final DataSource _dataSource; + final String _passwordKey; bool _accessAllowed = false; int? _dropdownValue; int? _initValue; @@ -89,8 +104,11 @@ class _NetworkDropdownFormFieldState extends State { required String? responseTagName, required String? labelText, required double width, - required String notPermittedMessage, required Duration flushBarDuration, + required translate.Localizations localizations, + required OilData oilData, + required DataSource dataSource, + required String passwordKey, }) : // _onAuthRequested = onAuthRequested, _allowedGroups = allowedGroups, @@ -101,7 +119,10 @@ class _NetworkDropdownFormFieldState extends State { _labelText = labelText, _width = width, _flushBarDuration = flushBarDuration, - _notPermittedMessage = notPermittedMessage, + _localizations = localizations, + _oilData = oilData, + _dataSource = dataSource, + _passwordKey = passwordKey, super(); /// @override @@ -255,7 +276,14 @@ class _NetworkDropdownFormFieldState extends State { return; } } - networkFieldAuthenticate(context, users).then((AuthResult authResult) { + networkFieldAuthenticate( + context, + users, + _passwordKey, + _localizations, + _dataSource, + flushbarDuration: _flushBarDuration + ).then((AuthResult authResult) { if (authResult.authenticated) { setState(() { _accessAllowed = true; @@ -266,7 +294,7 @@ class _NetworkDropdownFormFieldState extends State { } FlushbarHelper.createError( duration: _flushBarDuration, - message: _notPermittedMessage, + message: _localizations.tr('Editing is not permitted for current user'), ).show(context); _accessAllowed = false; } diff --git a/lib/src/edit_field/network_field_authenticate.dart b/lib/src/edit_field/network_field_authenticate.dart index f3e671e..ac64d77 100644 --- a/lib/src/edit_field/network_field_authenticate.dart +++ b/lib/src/edit_field/network_field_authenticate.dart @@ -1,15 +1,28 @@ import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_core/hmi_core_translate.dart' as translate; import 'package:hmi_networking/hmi_networking.dart'; import 'package:hmi_widgets/src/dialogs/auth_dialog.dart'; /// - Future networkFieldAuthenticate(BuildContext context, AppUserStacked users) { + Future networkFieldAuthenticate( + BuildContext context, + AppUserStacked users, + String passwordKey, + translate.Localizations localizations, + DataSource dataSource, { + Duration flushbarDuration = const Duration(milliseconds: 1000), + } + ) { const _debug = true; return Navigator.of(context).push( MaterialPageRoute( builder: (context) => AuthDialog( key: UniqueKey(), currentUser: users.peek, + passwordKey: passwordKey, + localizations: localizations, + dataSource: dataSource, + flushBarDuration: flushbarDuration, ), settings: const RouteSettings(name: "/authDialog"), ), diff --git a/lib/src/indicators/value_indicators/linear_value_indicator.dart b/lib/src/indicators/value_indicators/linear_value_indicator.dart index c6940a8..452c1c5 100644 --- a/lib/src/indicators/value_indicators/linear_value_indicator.dart +++ b/lib/src/indicators/value_indicators/linear_value_indicator.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; import 'package:hmi_widgets/src/core/relative_value.dart'; import 'package:hmi_widgets/src/theme/app_theme.dart'; - /// /// Линейный индикатор значения из потока [stream] . /// Значение в потоке может изменяться в диапазоне [min]...[max]. From 35c35c30f86a081a201993e71c031e50d4e5f0ab Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Thu, 2 Feb 2023 15:19:05 +0300 Subject: [PATCH 07/25] Added edit fields to example --- example/lib/core/fake_localizations.dart | 7 +++ example/lib/core/get_random_stream.dart | 5 +- example/lib/main.dart | 6 +- example/lib/pages/buttons/buttons_page.dart | 5 +- example/lib/pages/charts/fake_swl_data.dart | 11 ++-- .../pages/edit_fields/edit_fields_page.dart | 63 ++++++++++++++++--- .../lib/pages/edit_fields/fake_oil_data.dart | 11 ++++ example/lib/pages/home/home_menu_button.dart | 4 +- example/lib/pages/home/home_page.dart | 7 +-- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/pubspec.yaml | 5 +- .../network_edit_field.dart | 1 - 12 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 example/lib/core/fake_localizations.dart create mode 100644 example/lib/pages/edit_fields/fake_oil_data.dart diff --git a/example/lib/core/fake_localizations.dart b/example/lib/core/fake_localizations.dart new file mode 100644 index 0000000..4975bb8 --- /dev/null +++ b/example/lib/core/fake_localizations.dart @@ -0,0 +1,7 @@ +import 'package:hmi_core/hmi_core_translate.dart' as translate; +/// +class FakeLocalizations implements translate.Localizations { + const FakeLocalizations(); + @override + String tr(String value, {translate.AppLang? lng}) => value; +} \ No newline at end of file diff --git a/example/lib/core/get_random_stream.dart b/example/lib/core/get_random_stream.dart index 1ab67cb..12c5c78 100644 --- a/example/lib/core/get_random_stream.dart +++ b/example/lib/core/get_random_stream.dart @@ -1,7 +1,6 @@ import 'dart:math'; - import 'package:hmi_core/hmi_core.dart'; - +/// Stream> getRandomDataPointStream( T Function(Random) randomDelegate, { Duration duration = const Duration(seconds: 1), @@ -18,7 +17,7 @@ Stream> getRandomDataPointStream( duration: duration, ); } - +/// Stream getRandomStream( T Function(Random) randomDelegate, { Duration duration = const Duration(seconds: 1), diff --git a/example/lib/main.dart b/example/lib/main.dart index 96d59df..1ee6baa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,12 +1,14 @@ import 'package:example/pages/home/home_page.dart'; import 'package:flutter/material.dart'; - +/// void main() { runApp(const MyApp()); } - +/// class MyApp extends StatelessWidget { + /// const MyApp({super.key}); + // @override Widget build(BuildContext context) { return MaterialApp( diff --git a/example/lib/pages/buttons/buttons_page.dart b/example/lib/pages/buttons/buttons_page.dart index 1a97201..33038a0 100644 --- a/example/lib/pages/buttons/buttons_page.dart +++ b/example/lib/pages/buttons/buttons_page.dart @@ -1,10 +1,11 @@ import 'package:example/core/get_random_stream.dart'; import 'package:flutter/material.dart'; import 'package:hmi_widgets/hmi_widgets.dart'; - +/// class ButtonsPage extends StatelessWidget { + /// const ButtonsPage({super.key}); - + // @override Widget build(BuildContext context) { return Scaffold( diff --git a/example/lib/pages/charts/fake_swl_data.dart b/example/lib/pages/charts/fake_swl_data.dart index 6a7cb8f..ea46308 100644 --- a/example/lib/pages/charts/fake_swl_data.dart +++ b/example/lib/pages/charts/fake_swl_data.dart @@ -1,13 +1,12 @@ import 'dart:math'; - import 'package:hmi_widgets/hmi_widgets.dart'; - +/// class FakeSwlData implements SwlData { final double _rawWidth; final double _rawHeight; final int _pointsCount; final int _swlIndexesCount; - + /// FakeSwlData({ required double rawWidth, required double rawHeight, @@ -17,7 +16,7 @@ class FakeSwlData implements SwlData { _pointsCount = pointsCount, _rawHeight = rawHeight, _rawWidth = rawWidth; - + // @override Future> get x { final random = Random(); @@ -25,7 +24,7 @@ class FakeSwlData implements SwlData { List.generate(_pointsCount, (index) => random.nextDouble() * _rawWidth), ); } - + // @override Future> get y { final random = Random(); @@ -33,7 +32,7 @@ class FakeSwlData implements SwlData { List.generate(_pointsCount, (index) => random.nextDouble() * _rawHeight), ); } - + // @override Future>> get swl { final random = Random(); diff --git a/example/lib/pages/edit_fields/edit_fields_page.dart b/example/lib/pages/edit_fields/edit_fields_page.dart index 73add5a..137fb3d 100644 --- a/example/lib/pages/edit_fields/edit_fields_page.dart +++ b/example/lib/pages/edit_fields/edit_fields_page.dart @@ -1,9 +1,25 @@ +import 'package:example/core/fake_localizations.dart'; +import 'package:example/pages/edit_fields/fake_oil_data.dart'; import 'package:flutter/material.dart'; +import 'package:hmi_networking/hmi_networking.dart'; import 'package:hmi_widgets/hmi_widgets.dart'; - +/// class EditFieldsPage extends StatelessWidget { - const EditFieldsPage({super.key}); - + final _dataSource = DataSource({ + 'app-user': DataSet>( + params: ApiParams( { + 'api-sql': 'select', + 'tableName': 'app_user', + }), + apiRequest: const ApiRequest( + url: '127.0.0.1', + api: '/get-app-user', + port: 8080, + ), + ), + }); + EditFieldsPage({super.key}); + // @override Widget build(BuildContext context) { return Scaffold( @@ -11,14 +27,47 @@ class EditFieldsPage extends StatelessWidget { title: const Text('Edit Fields'), ), body: ListView( - padding: const EdgeInsets.all(4), children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: const [ + Text('Date Edit Field'), + SizedBox( + width: 300, + child: DateEditField(), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Text('Network Edit Field'), + SizedBox( + width: 300, + child: NetworkEditField( + localizations: const FakeLocalizations(), + dataSource: _dataSource, + passwordKey: 'passwordKey', + labelText: 'TestField', + ), + ), + ], + ), + ), Column( - children: const [ - Text('Date Edit Field'), + children: [ + const Text('Date Dropdown Form Field'), SizedBox( width: 300, - child: DateEditField(), + child: NetworkDropdownFormField( + localizations: const FakeLocalizations(), + dataSource: _dataSource, + passwordKey: 'passwordKey', + oilData: const FakeOilData(), + ), ), ], ), diff --git a/example/lib/pages/edit_fields/fake_oil_data.dart b/example/lib/pages/edit_fields/fake_oil_data.dart new file mode 100644 index 0000000..1c5a1e3 --- /dev/null +++ b/example/lib/pages/edit_fields/fake_oil_data.dart @@ -0,0 +1,11 @@ +import 'package:hmi_widgets/hmi_widgets.dart'; +/// +class FakeOilData implements OilData { + /// + const FakeOilData(); + // + @override + Future> names() => Future.value( + ['ISO VG68', 'ISO VG46', 'ISO VG32', 'ISO VG422'], + ); +} \ No newline at end of file diff --git a/example/lib/pages/home/home_menu_button.dart b/example/lib/pages/home/home_menu_button.dart index e861799..9d9cef9 100644 --- a/example/lib/pages/home/home_menu_button.dart +++ b/example/lib/pages/home/home_menu_button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; - +/// class HomeMenuButton extends StatelessWidget { final String text; final void Function()? onPressed; @@ -8,7 +8,7 @@ class HomeMenuButton extends StatelessWidget { required this.text, this.onPressed, }) : super(key: key); - + // @override Widget build(BuildContext context) { return TextButton( diff --git a/example/lib/pages/home/home_page.dart b/example/lib/pages/home/home_page.dart index 2244429..676ec2d 100644 --- a/example/lib/pages/home/home_page.dart +++ b/example/lib/pages/home/home_page.dart @@ -3,12 +3,11 @@ import 'package:example/pages/charts/charts_page.dart'; import 'package:example/pages/edit_fields/edit_fields_page.dart'; import 'package:example/pages/process/process_page.dart'; import 'package:flutter/material.dart'; - import 'home_menu_button.dart'; - +/// class HomePage extends StatelessWidget { const HomePage({super.key}); - + // @override Widget build(BuildContext context) { return Scaffold( @@ -52,7 +51,7 @@ class HomePage extends StatelessWidget { text: 'Edit fields', onPressed: () => Navigator.of(context).push( MaterialPageRoute( - builder: (_) => const EditFieldsPage(), + builder: (_) => EditFieldsPage(), ), ), ), diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..724bb2a 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index d4570bb..22de21e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,12 +15,13 @@ dependencies: hmi_core: git: url: https://github.com/a-givertzman/hmi_core.git - ref: master + ref: Move-to-hmi_core hmi_networking: git: url: https://github.com/a-givertzman/hmi_networking.git - ref: master + ref: Add-to-hmi_networking fl_chart: ^0.55.1 + another_flushbar: ^1.12.29 dev_dependencies: flutter_test: diff --git a/lib/src/edit_field/netword_edit_field/network_edit_field.dart b/lib/src/edit_field/netword_edit_field/network_edit_field.dart index 93e76e4..77d3446 100644 --- a/lib/src/edit_field/netword_edit_field/network_edit_field.dart +++ b/lib/src/edit_field/netword_edit_field/network_edit_field.dart @@ -46,7 +46,6 @@ class NetworkEditField extends StatefulWidget { String? unitText, double width = 230.0, showApplyButton = false, - String notPermittedMessage = 'Editing is not permitted for current user', Duration flushBarDuration = const Duration(milliseconds: 1000), required String passwordKey, required DataSource dataSource, From 4a2072cc6f328f44cd0ffb2edd32ac0370f8c618 Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Thu, 2 Feb 2023 18:10:22 +0300 Subject: [PATCH 08/25] Exported AuthDialog --- lib/hmi_widgets.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/hmi_widgets.dart b/lib/hmi_widgets.dart index af9383a..94c5db4 100644 --- a/lib/hmi_widgets.dart +++ b/lib/hmi_widgets.dart @@ -7,6 +7,7 @@ export 'src/theme/app_theme.dart'; export 'src/dialogs/complete_dialog.dart'; export 'src/dialogs/delete_dialog.dart'; export 'src/dialogs/failure_dialog.dart'; +export 'src/dialogs/auth_dialog.dart'; // // Buttons export 'src/buttons/circular_fab_widget.dart'; From 8e68c7eee5b83e1269a359e644fd53e01a381f53 Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Fri, 3 Feb 2023 16:36:44 +0300 Subject: [PATCH 09/25] Used refactored localization --- example/lib/core/fake_localizations.dart | 7 ---- .../pages/edit_fields/edit_fields_page.dart | 3 -- lib/src/dialogs/auth_dialog.dart | 35 +++++++------------ .../network_edit_field.dart | 11 +----- .../network_dropdown_field.dart | 11 +----- .../network_field_authenticate.dart | 3 -- 6 files changed, 15 insertions(+), 55 deletions(-) delete mode 100644 example/lib/core/fake_localizations.dart diff --git a/example/lib/core/fake_localizations.dart b/example/lib/core/fake_localizations.dart deleted file mode 100644 index 4975bb8..0000000 --- a/example/lib/core/fake_localizations.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:hmi_core/hmi_core_translate.dart' as translate; -/// -class FakeLocalizations implements translate.Localizations { - const FakeLocalizations(); - @override - String tr(String value, {translate.AppLang? lng}) => value; -} \ No newline at end of file diff --git a/example/lib/pages/edit_fields/edit_fields_page.dart b/example/lib/pages/edit_fields/edit_fields_page.dart index 137fb3d..f5dcf2c 100644 --- a/example/lib/pages/edit_fields/edit_fields_page.dart +++ b/example/lib/pages/edit_fields/edit_fields_page.dart @@ -1,4 +1,3 @@ -import 'package:example/core/fake_localizations.dart'; import 'package:example/pages/edit_fields/fake_oil_data.dart'; import 'package:flutter/material.dart'; import 'package:hmi_networking/hmi_networking.dart'; @@ -48,7 +47,6 @@ class EditFieldsPage extends StatelessWidget { SizedBox( width: 300, child: NetworkEditField( - localizations: const FakeLocalizations(), dataSource: _dataSource, passwordKey: 'passwordKey', labelText: 'TestField', @@ -63,7 +61,6 @@ class EditFieldsPage extends StatelessWidget { SizedBox( width: 300, child: NetworkDropdownFormField( - localizations: const FakeLocalizations(), dataSource: _dataSource, passwordKey: 'passwordKey', oilData: const FakeOilData(), diff --git a/lib/src/dialogs/auth_dialog.dart b/lib/src/dialogs/auth_dialog.dart index b2162cd..2f19b2f 100644 --- a/lib/src/dialogs/auth_dialog.dart +++ b/lib/src/dialogs/auth_dialog.dart @@ -1,13 +1,11 @@ import 'package:another_flushbar/flushbar_helper.dart'; import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; -import 'package:hmi_core/hmi_core_translate.dart' as translate; import 'package:hmi_networking/hmi_networking.dart'; /// class AuthDialog extends StatefulWidget { final AppUserSingle? _currentUser; final String _passwordKey; - final translate.Localizations _localizations; final Duration _flushBarDuration; final DataSource _dataSource; /// @@ -15,13 +13,11 @@ class AuthDialog extends StatefulWidget { Key? key, AppUserSingle? currentUser, required String passwordKey, - required translate.Localizations localizations, Duration flushBarDuration = const Duration(milliseconds: 1000), required DataSource dataSource, }) : _currentUser = currentUser, _passwordKey = passwordKey, - _localizations = localizations, _flushBarDuration = flushBarDuration, _dataSource = dataSource, super(key: key); @@ -31,7 +27,6 @@ class AuthDialog extends StatefulWidget { State createState() => _AuthDialogState( currentUser: _currentUser, passwordKey: _passwordKey, - localizations: _localizations, flushbarDuration: _flushBarDuration, dataSource: _dataSource, ); @@ -42,7 +37,6 @@ class _AuthDialogState extends State { static const _debug = true; final AppUserSingle? _currentUser; final String _passwordKey; - final translate.Localizations _localizations; final Duration _flushBarDuration; final DataSource _dataSource; late Authenticate _auth; @@ -51,13 +45,11 @@ class _AuthDialogState extends State { _AuthDialogState({ AppUserSingle? currentUser, required String passwordKey, - required translate.Localizations localizations, Duration flushbarDuration = const Duration(milliseconds: 1000), required DataSource dataSource, }) : _currentUser = currentUser, _passwordKey = passwordKey, - _localizations = localizations, _flushBarDuration = flushbarDuration, _dataSource = dataSource; @@ -71,7 +63,6 @@ class _AuthDialogState extends State { remote: _dataSource.dataSet('app-user'), ), passwordKey: _passwordKey, - localizations: _localizations, ); super.initState(); } @@ -94,7 +85,7 @@ class _AuthDialogState extends State { // padding: const EdgeInsets.all(paddingValue * 2), children: [ Text( - _localizations.tr('Please authenticate to continue...'), + Localized('Please authenticate to continue...').toString(), style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: paddingValue), @@ -114,7 +105,7 @@ class _AuthDialogState extends State { Icons.account_circle, ), prefixStyle: Theme.of(context).textTheme.bodyMedium, - labelText: _localizations.tr('Your login'), + labelText: Localized('Your login').toString(), labelStyle: Theme.of(context).textTheme.bodyMedium, errorMaxLines: 3, ), @@ -163,7 +154,7 @@ class _AuthDialogState extends State { _onComplete(context, true); }, child: Text( - _localizations.tr('Cancel'), + Localized('Cancel').toString(), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), @@ -175,7 +166,7 @@ class _AuthDialogState extends State { _onComplete(context, false); }, child: Text( - _localizations.tr('Next'), + Localized('Next').toString(), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), @@ -197,7 +188,7 @@ class _AuthDialogState extends State { Navigator.of(context).pop( AuthResult( authenticated: false, - message: _localizations.tr('Canceled by user'), + message: Localized('Canceled by user').toString(), user: AppUserSingle(remote: DataSet.empty()), ), ); @@ -206,8 +197,8 @@ class _AuthDialogState extends State { if ((currentUser != null) && (_userLogin.value() == '${currentUser['login']}')) { FlushbarHelper.createError( duration: _flushBarDuration, - title: _localizations.tr('Authentication'), - message: _localizations.tr('User already authenticated'), + title: Localized('Authentication').toString(), + message: Localized('User already authenticated').toString(), ).show(context); return ; } @@ -217,7 +208,7 @@ class _AuthDialogState extends State { if (authResult.authenticated) { FlushbarHelper.createSuccess( duration: _flushBarDuration, - title: _localizations.tr('Authentication'), + title: Localized('Authentication').toString(), message: authResult.message, ).show(context); Future.delayed(_flushBarDuration) @@ -227,7 +218,7 @@ class _AuthDialogState extends State { } else { FlushbarHelper.createError( duration: _flushBarDuration, - title: _localizations.tr('Authentication'), + title: Localized('Authentication').toString(), message: authResult.message, ).show(context); } @@ -237,7 +228,7 @@ class _AuthDialogState extends State { log(_debug, '[_AuthDialogState._onComplete] message: $message'); FlushbarHelper.createError( duration: _flushBarDuration, - title: _localizations.tr('Authentication'), + title: Localized('Authentication').toString(), message: message, ).show(context); } @@ -248,12 +239,12 @@ class _AuthDialogState extends State { String _buildWrongLoginPassMessage() { final wrongLoginMessage = _userLogin.validate().valid() ? '' - : _localizations.tr('Wrong login'); + : Localized('Wrong login').toString(); final wrongPassMessage = _userPass.validate().valid() ? '' - : _localizations.tr('Wrong password'); + : Localized('Wrong password').toString(); final andText = (wrongLoginMessage.isNotEmpty && wrongPassMessage.isNotEmpty) - ? ' ${_localizations.tr('and')} ' + ? ' ${Localized('and')} ' : ''; return '$wrongLoginMessage$andText$wrongPassMessage'; } diff --git a/lib/src/edit_field/netword_edit_field/network_edit_field.dart b/lib/src/edit_field/netword_edit_field/network_edit_field.dart index 77d3446..df5b4cc 100644 --- a/lib/src/edit_field/netword_edit_field/network_edit_field.dart +++ b/lib/src/edit_field/netword_edit_field/network_edit_field.dart @@ -2,7 +2,6 @@ import 'package:another_flushbar/flushbar_helper.dart'; import 'package:hmi_networking/hmi_networking.dart'; import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; -import 'package:hmi_core/hmi_core_translate.dart' as translate; import 'package:hmi_widgets/src/edit_field/network_field_authenticate.dart'; import 'package:hmi_widgets/src/theme/app_theme.dart'; @@ -24,7 +23,6 @@ class NetworkEditField extends StatefulWidget { final double _width; final bool _showApplyButton; final Duration _flushBarDuration; - final translate.Localizations _localizations; final DataSource _dataSource; final String _passwordKey; /// @@ -49,7 +47,6 @@ class NetworkEditField extends StatefulWidget { Duration flushBarDuration = const Duration(milliseconds: 1000), required String passwordKey, required DataSource dataSource, - required translate.Localizations localizations, }) : _allowedGroups = allowedGroups, _users = users, @@ -65,7 +62,6 @@ class NetworkEditField extends StatefulWidget { _flushBarDuration = flushBarDuration, _passwordKey = passwordKey, _dataSource = dataSource, - _localizations = localizations, super(key: key); /// @override @@ -83,7 +79,6 @@ class NetworkEditField extends StatefulWidget { width: _width, showApplyButton: _showApplyButton, flushBarDuration: _flushBarDuration, - localizations: _localizations, passwordKey: _passwordKey, dataSource: _dataSource, ); @@ -108,7 +103,6 @@ class _NetworkEditFieldState extends State> { final Duration _flushBarDuration; final DataSource _dataSource; final String _passwordKey; - final translate.Localizations _localizations; // bool _accessAllowed = false; String _initValue = ''; /// @@ -127,7 +121,6 @@ class _NetworkEditFieldState extends State> { required Duration flushBarDuration, required DataSource dataSource, required String passwordKey, - required translate.Localizations localizations, }) : assert(T == int || T == double, 'Generic must be int or double.'), _allowedGroups = allowedGroups, @@ -144,7 +137,6 @@ class _NetworkEditFieldState extends State> { _flushBarDuration = flushBarDuration, _dataSource = dataSource, _passwordKey = passwordKey, - _localizations = localizations, super(); /// @override @@ -362,7 +354,6 @@ class _NetworkEditFieldState extends State> { context, users, _passwordKey, - _localizations, _dataSource, flushbarDuration: _flushBarDuration, ).then((AuthResult authResult) { @@ -377,7 +368,7 @@ class _NetworkEditFieldState extends State> { } FlushbarHelper.createError( duration: _flushBarDuration, - message: _localizations.tr('Editing is not permitted for current user'), + message: Localized('Editing is not permitted for current user').toString(), ).show(context); _state.setAuthenticated(authenticated: false); // _accessAllowed = false; diff --git a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart index 9067156..530e0ab 100644 --- a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart +++ b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart @@ -2,7 +2,6 @@ import 'dart:core'; import 'package:hmi_networking/hmi_networking.dart'; import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; -import 'package:hmi_core/hmi_core_translate.dart' as translate; import 'package:another_flushbar/flushbar_helper.dart'; import 'package:hmi_widgets/src/edit_field/network_field_authenticate.dart'; import 'package:hmi_widgets/src/theme/app_theme.dart'; @@ -19,7 +18,6 @@ class NetworkDropdownFormField extends StatefulWidget { final String? _labelText; final double _width; final Duration _flushBarDuration; - final translate.Localizations _localizations; final DataSource _dataSource; final String _passwordKey; final OilData _oilData; @@ -35,7 +33,6 @@ class NetworkDropdownFormField extends StatefulWidget { String? labelText, double width = 350.0, Duration flushBarDuration = const Duration(milliseconds: 1000), - required translate.Localizations localizations, required DataSource dataSource, required String passwordKey, required OilData oilData, @@ -49,7 +46,6 @@ class NetworkDropdownFormField extends StatefulWidget { _labelText = labelText, _width = width, _flushBarDuration = flushBarDuration, - _localizations = localizations, _dataSource = dataSource, _passwordKey = passwordKey, _oilData = oilData, @@ -66,7 +62,6 @@ class NetworkDropdownFormField extends StatefulWidget { labelText: _labelText, width: _width, flushBarDuration: _flushBarDuration, - localizations: _localizations, dataSource: _dataSource, passwordKey: _passwordKey, oilData: _oilData, @@ -87,7 +82,6 @@ class _NetworkDropdownFormFieldState extends State { final String? _labelText; final double _width; final Duration _flushBarDuration; - final translate.Localizations _localizations; final DataSource _dataSource; final String _passwordKey; bool _accessAllowed = false; @@ -105,7 +99,6 @@ class _NetworkDropdownFormFieldState extends State { required String? labelText, required double width, required Duration flushBarDuration, - required translate.Localizations localizations, required OilData oilData, required DataSource dataSource, required String passwordKey, @@ -119,7 +112,6 @@ class _NetworkDropdownFormFieldState extends State { _labelText = labelText, _width = width, _flushBarDuration = flushBarDuration, - _localizations = localizations, _oilData = oilData, _dataSource = dataSource, _passwordKey = passwordKey, @@ -280,7 +272,6 @@ class _NetworkDropdownFormFieldState extends State { context, users, _passwordKey, - _localizations, _dataSource, flushbarDuration: _flushBarDuration ).then((AuthResult authResult) { @@ -294,7 +285,7 @@ class _NetworkDropdownFormFieldState extends State { } FlushbarHelper.createError( duration: _flushBarDuration, - message: _localizations.tr('Editing is not permitted for current user'), + message: Localized('Editing is not permitted for current user').toString(), ).show(context); _accessAllowed = false; } diff --git a/lib/src/edit_field/network_field_authenticate.dart b/lib/src/edit_field/network_field_authenticate.dart index ac64d77..7f140f8 100644 --- a/lib/src/edit_field/network_field_authenticate.dart +++ b/lib/src/edit_field/network_field_authenticate.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hmi_core/hmi_core.dart'; -import 'package:hmi_core/hmi_core_translate.dart' as translate; import 'package:hmi_networking/hmi_networking.dart'; import 'package:hmi_widgets/src/dialogs/auth_dialog.dart'; /// @@ -8,7 +7,6 @@ import 'package:hmi_widgets/src/dialogs/auth_dialog.dart'; BuildContext context, AppUserStacked users, String passwordKey, - translate.Localizations localizations, DataSource dataSource, { Duration flushbarDuration = const Duration(milliseconds: 1000), } @@ -20,7 +18,6 @@ import 'package:hmi_widgets/src/dialogs/auth_dialog.dart'; key: UniqueKey(), currentUser: users.peek, passwordKey: passwordKey, - localizations: localizations, dataSource: dataSource, flushBarDuration: flushbarDuration, ), From bb34132ea9a37c47a910a1b7d18eb084c7be222a Mon Sep 17 00:00:00 2001 From: anton lobanov Date: Fri, 3 Feb 2023 20:40:28 +0300 Subject: [PATCH 10/25] Localized --- lib/src/dialogs/auth_dialog.dart | 45 +++++++------------ .../network_edit_field.dart | 2 +- .../network_dropdown_field.dart | 2 +- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/lib/src/dialogs/auth_dialog.dart b/lib/src/dialogs/auth_dialog.dart index b2162cd..7e786a5 100644 --- a/lib/src/dialogs/auth_dialog.dart +++ b/lib/src/dialogs/auth_dialog.dart @@ -7,9 +7,7 @@ import 'package:hmi_networking/hmi_networking.dart'; class AuthDialog extends StatefulWidget { final AppUserSingle? _currentUser; final String _passwordKey; - final translate.Localizations _localizations; final Duration _flushBarDuration; - final DataSource _dataSource; /// const AuthDialog({ Key? key, @@ -21,9 +19,7 @@ class AuthDialog extends StatefulWidget { }) : _currentUser = currentUser, _passwordKey = passwordKey, - _localizations = localizations, _flushBarDuration = flushBarDuration, - _dataSource = dataSource, super(key: key); /// @override @@ -31,9 +27,7 @@ class AuthDialog extends StatefulWidget { State createState() => _AuthDialogState( currentUser: _currentUser, passwordKey: _passwordKey, - localizations: _localizations, flushbarDuration: _flushBarDuration, - dataSource: _dataSource, ); } @@ -42,24 +36,18 @@ class _AuthDialogState extends State { static const _debug = true; final AppUserSingle? _currentUser; final String _passwordKey; - final translate.Localizations _localizations; final Duration _flushBarDuration; - final DataSource _dataSource; late Authenticate _auth; late UserLogin _userLogin; late UserPassword _userPass; _AuthDialogState({ AppUserSingle? currentUser, required String passwordKey, - required translate.Localizations localizations, Duration flushbarDuration = const Duration(milliseconds: 1000), - required DataSource dataSource, }) : _currentUser = currentUser, _passwordKey = passwordKey, - _localizations = localizations, - _flushBarDuration = flushbarDuration, - _dataSource = dataSource; + _flushBarDuration = flushbarDuration; /// @override @@ -67,11 +55,8 @@ class _AuthDialogState extends State { _userLogin = const UserLogin(value: ''); _userPass = UserPassword(value: '', key: _passwordKey); _auth = Authenticate( - user: AppUserSingle( - remote: _dataSource.dataSet('app-user'), - ), + user: AppUserSingle(), passwordKey: _passwordKey, - localizations: _localizations, ); super.initState(); } @@ -94,7 +79,7 @@ class _AuthDialogState extends State { // padding: const EdgeInsets.all(paddingValue * 2), children: [ Text( - _localizations.tr('Please authenticate to continue...'), + const Localized('Please authenticate to continue...').toString(), style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: paddingValue), @@ -114,7 +99,7 @@ class _AuthDialogState extends State { Icons.account_circle, ), prefixStyle: Theme.of(context).textTheme.bodyMedium, - labelText: _localizations.tr('Your login'), + labelText: const Localized('Your login').toString(), labelStyle: Theme.of(context).textTheme.bodyMedium, errorMaxLines: 3, ), @@ -163,7 +148,7 @@ class _AuthDialogState extends State { _onComplete(context, true); }, child: Text( - _localizations.tr('Cancel'), + const Localized('Cancel').toString(), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), @@ -175,7 +160,7 @@ class _AuthDialogState extends State { _onComplete(context, false); }, child: Text( - _localizations.tr('Next'), + const Localized('Next').toString(), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), @@ -197,7 +182,7 @@ class _AuthDialogState extends State { Navigator.of(context).pop( AuthResult( authenticated: false, - message: _localizations.tr('Canceled by user'), + message: const Localized('Canceled by user').toString(), user: AppUserSingle(remote: DataSet.empty()), ), ); @@ -206,8 +191,8 @@ class _AuthDialogState extends State { if ((currentUser != null) && (_userLogin.value() == '${currentUser['login']}')) { FlushbarHelper.createError( duration: _flushBarDuration, - title: _localizations.tr('Authentication'), - message: _localizations.tr('User already authenticated'), + title: const Localized('Authentication').toString(), + message: const Localized('User already authenticated').toString(), ).show(context); return ; } @@ -217,7 +202,7 @@ class _AuthDialogState extends State { if (authResult.authenticated) { FlushbarHelper.createSuccess( duration: _flushBarDuration, - title: _localizations.tr('Authentication'), + title: const Localized('Authentication').toString(), message: authResult.message, ).show(context); Future.delayed(_flushBarDuration) @@ -227,7 +212,7 @@ class _AuthDialogState extends State { } else { FlushbarHelper.createError( duration: _flushBarDuration, - title: _localizations.tr('Authentication'), + title: const Localized('Authentication').toString(), message: authResult.message, ).show(context); } @@ -237,7 +222,7 @@ class _AuthDialogState extends State { log(_debug, '[_AuthDialogState._onComplete] message: $message'); FlushbarHelper.createError( duration: _flushBarDuration, - title: _localizations.tr('Authentication'), + title: const Localized('Authentication').toString(), message: message, ).show(context); } @@ -248,12 +233,12 @@ class _AuthDialogState extends State { String _buildWrongLoginPassMessage() { final wrongLoginMessage = _userLogin.validate().valid() ? '' - : _localizations.tr('Wrong login'); + : const Localized('Wrong login').toString(); final wrongPassMessage = _userPass.validate().valid() ? '' - : _localizations.tr('Wrong password'); + : const Localized('Wrong password').toString(); final andText = (wrongLoginMessage.isNotEmpty && wrongPassMessage.isNotEmpty) - ? ' ${_localizations.tr('and')} ' + ? ' ${const Localized('and')} .toString()' : ''; return '$wrongLoginMessage$andText$wrongPassMessage'; } diff --git a/lib/src/edit_field/netword_edit_field/network_edit_field.dart b/lib/src/edit_field/netword_edit_field/network_edit_field.dart index 77d3446..661ca21 100644 --- a/lib/src/edit_field/netword_edit_field/network_edit_field.dart +++ b/lib/src/edit_field/netword_edit_field/network_edit_field.dart @@ -377,7 +377,7 @@ class _NetworkEditFieldState extends State> { } FlushbarHelper.createError( duration: _flushBarDuration, - message: _localizations.tr('Editing is not permitted for current user'), + message: const Localized('Editing is not permitted for current user').toString(), ).show(context); _state.setAuthenticated(authenticated: false); // _accessAllowed = false; diff --git a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart index 9067156..f0a645a 100644 --- a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart +++ b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart @@ -294,7 +294,7 @@ class _NetworkDropdownFormFieldState extends State { } FlushbarHelper.createError( duration: _flushBarDuration, - message: _localizations.tr('Editing is not permitted for current user'), + message: Localized('Editing is not permitted for current user').toString(), ).show(context); _accessAllowed = false; } From 1c39f93bd75506b1d4b2b5d715dcbd2306065717 Mon Sep 17 00:00:00 2001 From: anton lobanov Date: Fri, 3 Feb 2023 20:51:22 +0300 Subject: [PATCH 11/25] unused DataSource removed --- .../pages/edit_fields/edit_fields_page.dart | 25 ++++--------------- lib/src/dialogs/auth_dialog.dart | 1 - .../network_edit_field.dart | 8 ------ .../network_dropdown_field.dart | 10 +------- .../network_field_authenticate.dart | 4 +-- 5 files changed, 7 insertions(+), 41 deletions(-) diff --git a/example/lib/pages/edit_fields/edit_fields_page.dart b/example/lib/pages/edit_fields/edit_fields_page.dart index f5dcf2c..5a19a4a 100644 --- a/example/lib/pages/edit_fields/edit_fields_page.dart +++ b/example/lib/pages/edit_fields/edit_fields_page.dart @@ -4,19 +4,6 @@ import 'package:hmi_networking/hmi_networking.dart'; import 'package:hmi_widgets/hmi_widgets.dart'; /// class EditFieldsPage extends StatelessWidget { - final _dataSource = DataSource({ - 'app-user': DataSet>( - params: ApiParams( { - 'api-sql': 'select', - 'tableName': 'app_user', - }), - apiRequest: const ApiRequest( - url: '127.0.0.1', - api: '/get-app-user', - port: 8080, - ), - ), - }); EditFieldsPage({super.key}); // @override @@ -42,12 +29,11 @@ class EditFieldsPage extends StatelessWidget { Padding( padding: const EdgeInsets.all(16.0), child: Column( - children: [ - const Text('Network Edit Field'), + children: const [ + Text('Network Edit Field'), SizedBox( width: 300, child: NetworkEditField( - dataSource: _dataSource, passwordKey: 'passwordKey', labelText: 'TestField', ), @@ -56,14 +42,13 @@ class EditFieldsPage extends StatelessWidget { ), ), Column( - children: [ - const Text('Date Dropdown Form Field'), + children: const [ + Text('Date Dropdown Form Field'), SizedBox( width: 300, child: NetworkDropdownFormField( - dataSource: _dataSource, passwordKey: 'passwordKey', - oilData: const FakeOilData(), + oilData: FakeOilData(), ), ), ], diff --git a/lib/src/dialogs/auth_dialog.dart b/lib/src/dialogs/auth_dialog.dart index 722871f..15cdc9e 100644 --- a/lib/src/dialogs/auth_dialog.dart +++ b/lib/src/dialogs/auth_dialog.dart @@ -13,7 +13,6 @@ class AuthDialog extends StatefulWidget { AppUserSingle? currentUser, required String passwordKey, Duration flushBarDuration = const Duration(milliseconds: 1000), - required DataSource dataSource, }) : _currentUser = currentUser, _passwordKey = passwordKey, diff --git a/lib/src/edit_field/netword_edit_field/network_edit_field.dart b/lib/src/edit_field/netword_edit_field/network_edit_field.dart index 3c441c9..c5cb7ae 100644 --- a/lib/src/edit_field/netword_edit_field/network_edit_field.dart +++ b/lib/src/edit_field/netword_edit_field/network_edit_field.dart @@ -23,7 +23,6 @@ class NetworkEditField extends StatefulWidget { final double _width; final bool _showApplyButton; final Duration _flushBarDuration; - final DataSource _dataSource; final String _passwordKey; /// /// - [writeTagName] - the name of DataServer tag to send value @@ -46,7 +45,6 @@ class NetworkEditField extends StatefulWidget { showApplyButton = false, Duration flushBarDuration = const Duration(milliseconds: 1000), required String passwordKey, - required DataSource dataSource, }) : _allowedGroups = allowedGroups, _users = users, @@ -61,7 +59,6 @@ class NetworkEditField extends StatefulWidget { _showApplyButton = showApplyButton, _flushBarDuration = flushBarDuration, _passwordKey = passwordKey, - _dataSource = dataSource, super(key: key); /// @override @@ -80,7 +77,6 @@ class NetworkEditField extends StatefulWidget { showApplyButton: _showApplyButton, flushBarDuration: _flushBarDuration, passwordKey: _passwordKey, - dataSource: _dataSource, ); } @@ -101,7 +97,6 @@ class _NetworkEditFieldState extends State> { final double _width; final bool _showApplyButton; final Duration _flushBarDuration; - final DataSource _dataSource; final String _passwordKey; // bool _accessAllowed = false; String _initValue = ''; @@ -119,7 +114,6 @@ class _NetworkEditFieldState extends State> { required double width, required bool showApplyButton, required Duration flushBarDuration, - required DataSource dataSource, required String passwordKey, }) : assert(T == int || T == double, 'Generic must be int or double.'), @@ -135,7 +129,6 @@ class _NetworkEditFieldState extends State> { _width = width, _showApplyButton = showApplyButton, _flushBarDuration = flushBarDuration, - _dataSource = dataSource, _passwordKey = passwordKey, super(); /// @@ -354,7 +347,6 @@ class _NetworkEditFieldState extends State> { context, users, _passwordKey, - _dataSource, flushbarDuration: _flushBarDuration, ).then((AuthResult authResult) { if (authResult.authenticated) { diff --git a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart index 530e0ab..7629a22 100644 --- a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart +++ b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart @@ -18,7 +18,6 @@ class NetworkDropdownFormField extends StatefulWidget { final String? _labelText; final double _width; final Duration _flushBarDuration; - final DataSource _dataSource; final String _passwordKey; final OilData _oilData; /// @@ -33,7 +32,6 @@ class NetworkDropdownFormField extends StatefulWidget { String? labelText, double width = 350.0, Duration flushBarDuration = const Duration(milliseconds: 1000), - required DataSource dataSource, required String passwordKey, required OilData oilData, }) : @@ -46,7 +44,6 @@ class NetworkDropdownFormField extends StatefulWidget { _labelText = labelText, _width = width, _flushBarDuration = flushBarDuration, - _dataSource = dataSource, _passwordKey = passwordKey, _oilData = oilData, super(key: key); @@ -62,7 +59,6 @@ class NetworkDropdownFormField extends StatefulWidget { labelText: _labelText, width: _width, flushBarDuration: _flushBarDuration, - dataSource: _dataSource, passwordKey: _passwordKey, oilData: _oilData, ); @@ -82,7 +78,6 @@ class _NetworkDropdownFormFieldState extends State { final String? _labelText; final double _width; final Duration _flushBarDuration; - final DataSource _dataSource; final String _passwordKey; bool _accessAllowed = false; int? _dropdownValue; @@ -100,7 +95,6 @@ class _NetworkDropdownFormFieldState extends State { required double width, required Duration flushBarDuration, required OilData oilData, - required DataSource dataSource, required String passwordKey, }) : // _onAuthRequested = onAuthRequested, @@ -113,7 +107,6 @@ class _NetworkDropdownFormFieldState extends State { _width = width, _flushBarDuration = flushBarDuration, _oilData = oilData, - _dataSource = dataSource, _passwordKey = passwordKey, super(); /// @@ -176,7 +169,7 @@ class _NetworkDropdownFormFieldState extends State { ), suffixIcon: _buildSufixIcon(), filled: true, - fillColor: Theme.of(context).backgroundColor, + fillColor: Theme.of(context).colorScheme.background, ), alignment: AlignmentDirectional.centerEnd, items: _buildDropdownMenuItems(context, _oilNames, _dropMenuItemWidth), @@ -272,7 +265,6 @@ class _NetworkDropdownFormFieldState extends State { context, users, _passwordKey, - _dataSource, flushbarDuration: _flushBarDuration ).then((AuthResult authResult) { if (authResult.authenticated) { diff --git a/lib/src/edit_field/network_field_authenticate.dart b/lib/src/edit_field/network_field_authenticate.dart index 7f140f8..9de8f42 100644 --- a/lib/src/edit_field/network_field_authenticate.dart +++ b/lib/src/edit_field/network_field_authenticate.dart @@ -6,8 +6,7 @@ import 'package:hmi_widgets/src/dialogs/auth_dialog.dart'; Future networkFieldAuthenticate( BuildContext context, AppUserStacked users, - String passwordKey, - DataSource dataSource, { + String passwordKey, { Duration flushbarDuration = const Duration(milliseconds: 1000), } ) { @@ -18,7 +17,6 @@ import 'package:hmi_widgets/src/dialogs/auth_dialog.dart'; key: UniqueKey(), currentUser: users.peek, passwordKey: passwordKey, - dataSource: dataSource, flushBarDuration: flushbarDuration, ), settings: const RouteSettings(name: "/authDialog"), From 92c038869516009d07a3cf04b53479bb4128f751 Mon Sep 17 00:00:00 2001 From: anton lobanov Date: Fri, 3 Feb 2023 20:52:25 +0300 Subject: [PATCH 12/25] unused DataSource removed --- example/lib/main.dart | 14 ++++++++++++++ .../lib/pages/edit_fields/edit_fields_page.dart | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 1ee6baa..cf39787 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,21 @@ import 'package:example/pages/home/home_page.dart'; import 'package:flutter/material.dart'; +import 'package:hmi_networking/hmi_networking.dart'; /// void main() { + DataSource.initialize({ + 'app-user': DataSet>( + params: ApiParams( { + 'api-sql': 'select', + 'tableName': 'app_user', + }), + apiRequest: const ApiRequest( + url: '127.0.0.1', + api: '/get-app-user', + port: 8080, + ), + ), + }); runApp(const MyApp()); } /// diff --git a/example/lib/pages/edit_fields/edit_fields_page.dart b/example/lib/pages/edit_fields/edit_fields_page.dart index 5a19a4a..a493e6b 100644 --- a/example/lib/pages/edit_fields/edit_fields_page.dart +++ b/example/lib/pages/edit_fields/edit_fields_page.dart @@ -1,10 +1,10 @@ import 'package:example/pages/edit_fields/fake_oil_data.dart'; import 'package:flutter/material.dart'; -import 'package:hmi_networking/hmi_networking.dart'; import 'package:hmi_widgets/hmi_widgets.dart'; /// class EditFieldsPage extends StatelessWidget { - EditFieldsPage({super.key}); + /// + const EditFieldsPage({super.key}); // @override Widget build(BuildContext context) { From ca34c0404e1cbf2892565ed219389911177f2419 Mon Sep 17 00:00:00 2001 From: anton lobanov Date: Fri, 3 Feb 2023 20:54:37 +0300 Subject: [PATCH 13/25] static analisis issues fixed --- example/lib/pages/home/home_page.dart | 2 +- lib/src/edit_field/date_edit_field/date_edit_field.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/lib/pages/home/home_page.dart b/example/lib/pages/home/home_page.dart index 676ec2d..6892722 100644 --- a/example/lib/pages/home/home_page.dart +++ b/example/lib/pages/home/home_page.dart @@ -51,7 +51,7 @@ class HomePage extends StatelessWidget { text: 'Edit fields', onPressed: () => Navigator.of(context).push( MaterialPageRoute( - builder: (_) => EditFieldsPage(), + builder: (_) => const EditFieldsPage(), ), ), ), diff --git a/lib/src/edit_field/date_edit_field/date_edit_field.dart b/lib/src/edit_field/date_edit_field/date_edit_field.dart index 22cd3e9..3068fd6 100644 --- a/lib/src/edit_field/date_edit_field/date_edit_field.dart +++ b/lib/src/edit_field/date_edit_field/date_edit_field.dart @@ -65,7 +65,7 @@ class _DateEditFieldState extends State { label: _label != null ? Text(_label!) : null, errorStyle: TextStyle(height: 0), errorBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Theme.of(context).errorColor, width: 2.0), + borderSide: BorderSide(color: Theme.of(context).colorScheme.error, width: 2.0), ), suffix: Row( mainAxisSize: MainAxisSize.min, From d1eadae74f7cdabdc54588842e1eae52523b9bb8 Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 8 Feb 2023 12:47:07 +0300 Subject: [PATCH 14/25] Took flushBarDuration and passwordKey from settings, made use of 'v' getter from Localized --- .../pages/edit_fields/edit_fields_page.dart | 4 +- example/pubspec.yaml | 2 +- lib/src/dialogs/auth_dialog.dart | 59 +++++++++---------- .../network_edit_field.dart | 23 +++----- .../network_dropdown_field.dart | 23 +++----- .../network_field_authenticate.dart | 9 ++- pubspec.yaml | 2 +- 7 files changed, 50 insertions(+), 72 deletions(-) diff --git a/example/lib/pages/edit_fields/edit_fields_page.dart b/example/lib/pages/edit_fields/edit_fields_page.dart index a493e6b..70f8313 100644 --- a/example/lib/pages/edit_fields/edit_fields_page.dart +++ b/example/lib/pages/edit_fields/edit_fields_page.dart @@ -34,7 +34,6 @@ class EditFieldsPage extends StatelessWidget { SizedBox( width: 300, child: NetworkEditField( - passwordKey: 'passwordKey', labelText: 'TestField', ), ), @@ -43,11 +42,10 @@ class EditFieldsPage extends StatelessWidget { ), Column( children: const [ - Text('Date Dropdown Form Field'), + Text('Network Dropdown Form Field'), SizedBox( width: 300, child: NetworkDropdownFormField( - passwordKey: 'passwordKey', oilData: FakeOilData(), ), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 22de21e..9077d21 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: hmi_core: git: url: https://github.com/a-givertzman/hmi_core.git - ref: Move-to-hmi_core + ref: Localized-refactoring hmi_networking: git: url: https://github.com/a-givertzman/hmi_networking.git diff --git a/lib/src/dialogs/auth_dialog.dart b/lib/src/dialogs/auth_dialog.dart index 15cdc9e..cd9eefe 100644 --- a/lib/src/dialogs/auth_dialog.dart +++ b/lib/src/dialogs/auth_dialog.dart @@ -5,17 +5,14 @@ import 'package:hmi_networking/hmi_networking.dart'; /// class AuthDialog extends StatefulWidget { final AppUserSingle? _currentUser; - final String _passwordKey; - final Duration _flushBarDuration; + final Duration? _flushBarDuration; /// const AuthDialog({ Key? key, AppUserSingle? currentUser, - required String passwordKey, - Duration flushBarDuration = const Duration(milliseconds: 1000), + Duration? flushBarDuration, }) : _currentUser = currentUser, - _passwordKey = passwordKey, _flushBarDuration = flushBarDuration, super(key: key); /// @@ -23,7 +20,6 @@ class AuthDialog extends StatefulWidget { // ignore: no_logic_in_create_state State createState() => _AuthDialogState( currentUser: _currentUser, - passwordKey: _passwordKey, flushbarDuration: _flushBarDuration, ); } @@ -32,28 +28,24 @@ class AuthDialog extends StatefulWidget { class _AuthDialogState extends State { static const _debug = true; final AppUserSingle? _currentUser; - final String _passwordKey; - final Duration _flushBarDuration; + final Duration? _flushBarDuration; late Authenticate _auth; late UserLogin _userLogin; late UserPassword _userPass; _AuthDialogState({ AppUserSingle? currentUser, - required String passwordKey, - Duration flushbarDuration = const Duration(milliseconds: 1000), + Duration? flushbarDuration, }) : _currentUser = currentUser, - _passwordKey = passwordKey, _flushBarDuration = flushbarDuration; /// @override void initState() { _userLogin = const UserLogin(value: ''); - _userPass = UserPassword(value: '', key: _passwordKey); + _userPass = UserPassword(value: ''); _auth = Authenticate( user: AppUserSingle(), - passwordKey: _passwordKey, ); super.initState(); } @@ -76,7 +68,7 @@ class _AuthDialogState extends State { // padding: const EdgeInsets.all(paddingValue * 2), children: [ Text( - const Localized('Please authenticate to continue...').toString(), + const Localized('Please authenticate to continue...').v, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: paddingValue), @@ -96,7 +88,7 @@ class _AuthDialogState extends State { Icons.account_circle, ), prefixStyle: Theme.of(context).textTheme.bodyMedium, - labelText: const Localized('Your login').toString(), + labelText: const Localized('Your login').v, labelStyle: Theme.of(context).textTheme.bodyMedium, errorMaxLines: 3, ), @@ -128,7 +120,7 @@ class _AuthDialogState extends State { autocorrect: false, onChanged: (value) { setState(() { - _userPass = UserPassword(value: value, key: _passwordKey); + _userPass = UserPassword(value: value); }); }, ), @@ -145,7 +137,7 @@ class _AuthDialogState extends State { _onComplete(context, true); }, child: Text( - const Localized('Cancel').toString(), + const Localized('Cancel').v, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), @@ -157,7 +149,7 @@ class _AuthDialogState extends State { _onComplete(context, false); }, child: Text( - const Localized('Next').toString(), + const Localized('Next').v, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), @@ -174,12 +166,15 @@ class _AuthDialogState extends State { } /// void _onComplete(BuildContext context, bool cancel) { + final flushBarDuration = _flushBarDuration ?? Duration( + milliseconds: AppUiSettingsNum.getSetting('flushBarDurationMedium') as int, + ); log(_debug, '[_AuthDialogState._onComplete] cancel: $cancel'); if (cancel) { Navigator.of(context).pop( AuthResult( authenticated: false, - message: const Localized('Canceled by user').toString(), + message: const Localized('Canceled by user').v, user: AppUserSingle(remote: DataSet.empty()), ), ); @@ -187,9 +182,9 @@ class _AuthDialogState extends State { final currentUser = _currentUser; if ((currentUser != null) && (_userLogin.value() == '${currentUser['login']}')) { FlushbarHelper.createError( - duration: _flushBarDuration, - title: const Localized('Authentication').toString(), - message: const Localized('User already authenticated').toString(), + duration: flushBarDuration, + title: const Localized('Authentication').v, + message: const Localized('User already authenticated').v, ).show(context); return ; } @@ -198,18 +193,18 @@ class _AuthDialogState extends State { .then((authResult) { if (authResult.authenticated) { FlushbarHelper.createSuccess( - duration: _flushBarDuration, - title: const Localized('Authentication').toString(), + duration: flushBarDuration, + title: const Localized('Authentication').v, message: authResult.message, ).show(context); - Future.delayed(_flushBarDuration) + Future.delayed(flushBarDuration) .then((_) { Navigator.of(context).pop(authResult); }); } else { FlushbarHelper.createError( - duration: _flushBarDuration, - title: const Localized('Authentication').toString(), + duration: flushBarDuration, + title: const Localized('Authentication').v, message: authResult.message, ).show(context); } @@ -218,8 +213,8 @@ class _AuthDialogState extends State { final message = _buildWrongLoginPassMessage(); log(_debug, '[_AuthDialogState._onComplete] message: $message'); FlushbarHelper.createError( - duration: _flushBarDuration, - title: const Localized('Authentication').toString(), + duration: flushBarDuration, + title: const Localized('Authentication').v, message: message, ).show(context); } @@ -230,12 +225,12 @@ class _AuthDialogState extends State { String _buildWrongLoginPassMessage() { final wrongLoginMessage = _userLogin.validate().valid() ? '' - : const Localized('Wrong login').toString(); + : const Localized('Wrong login').v; final wrongPassMessage = _userPass.validate().valid() ? '' - : const Localized('Wrong password').toString(); + : const Localized('Wrong password').v; final andText = (wrongLoginMessage.isNotEmpty && wrongPassMessage.isNotEmpty) - ? ' ${const Localized('and')} .toString()' + ? ' ${const Localized('and')}' : ''; return '$wrongLoginMessage$andText$wrongPassMessage'; } diff --git a/lib/src/edit_field/netword_edit_field/network_edit_field.dart b/lib/src/edit_field/netword_edit_field/network_edit_field.dart index c5cb7ae..45358b5 100644 --- a/lib/src/edit_field/netword_edit_field/network_edit_field.dart +++ b/lib/src/edit_field/netword_edit_field/network_edit_field.dart @@ -22,8 +22,7 @@ class NetworkEditField extends StatefulWidget { final String? _unitText; final double _width; final bool _showApplyButton; - final Duration _flushBarDuration; - final String _passwordKey; + final Duration? _flushBarDuration; /// /// - [writeTagName] - the name of DataServer tag to send value /// - [responseTagName] - the name of DataServer tag to get response if value written @@ -43,8 +42,7 @@ class NetworkEditField extends StatefulWidget { String? unitText, double width = 230.0, showApplyButton = false, - Duration flushBarDuration = const Duration(milliseconds: 1000), - required String passwordKey, + Duration? flushBarDuration, }) : _allowedGroups = allowedGroups, _users = users, @@ -58,7 +56,6 @@ class NetworkEditField extends StatefulWidget { _width = width, _showApplyButton = showApplyButton, _flushBarDuration = flushBarDuration, - _passwordKey = passwordKey, super(key: key); /// @override @@ -76,7 +73,6 @@ class NetworkEditField extends StatefulWidget { width: _width, showApplyButton: _showApplyButton, flushBarDuration: _flushBarDuration, - passwordKey: _passwordKey, ); } @@ -96,8 +92,7 @@ class _NetworkEditFieldState extends State> { final String? _unitText; final double _width; final bool _showApplyButton; - final Duration _flushBarDuration; - final String _passwordKey; + final Duration? _flushBarDuration; // bool _accessAllowed = false; String _initValue = ''; /// @@ -113,8 +108,7 @@ class _NetworkEditFieldState extends State> { required String? unitText, required double width, required bool showApplyButton, - required Duration flushBarDuration, - required String passwordKey, + Duration? flushBarDuration, }) : assert(T == int || T == double, 'Generic must be int or double.'), _allowedGroups = allowedGroups, @@ -129,7 +123,6 @@ class _NetworkEditFieldState extends State> { _width = width, _showApplyButton = showApplyButton, _flushBarDuration = flushBarDuration, - _passwordKey = passwordKey, super(); /// @override @@ -346,8 +339,6 @@ class _NetworkEditFieldState extends State> { networkFieldAuthenticate( context, users, - _passwordKey, - flushbarDuration: _flushBarDuration, ).then((AuthResult authResult) { if (authResult.authenticated) { setState(() { @@ -359,8 +350,10 @@ class _NetworkEditFieldState extends State> { }); } FlushbarHelper.createError( - duration: _flushBarDuration, - message: const Localized('Editing is not permitted for current user').toString(), + duration: _flushBarDuration ?? Duration( + milliseconds: AppUiSettingsNum.getSetting('flushBarDurationMedium') as int, + ), + message: const Localized('Editing is not permitted for current user').v, ).show(context); _state.setAuthenticated(authenticated: false); // _accessAllowed = false; diff --git a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart index 7629a22..a7699b9 100644 --- a/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart +++ b/lib/src/edit_field/network_dropdown_field/network_dropdown_field.dart @@ -17,8 +17,7 @@ class NetworkDropdownFormField extends StatefulWidget { final String? _responseTagName; final String? _labelText; final double _width; - final Duration _flushBarDuration; - final String _passwordKey; + final Duration? _flushBarDuration; final OilData _oilData; /// const NetworkDropdownFormField({ @@ -31,8 +30,7 @@ class NetworkDropdownFormField extends StatefulWidget { String? responseTagName, String? labelText, double width = 350.0, - Duration flushBarDuration = const Duration(milliseconds: 1000), - required String passwordKey, + Duration? flushBarDuration, required OilData oilData, }) : // _onAuthRequested = onAuthRequested, @@ -44,7 +42,6 @@ class NetworkDropdownFormField extends StatefulWidget { _labelText = labelText, _width = width, _flushBarDuration = flushBarDuration, - _passwordKey = passwordKey, _oilData = oilData, super(key: key); /// @@ -59,7 +56,6 @@ class NetworkDropdownFormField extends StatefulWidget { labelText: _labelText, width: _width, flushBarDuration: _flushBarDuration, - passwordKey: _passwordKey, oilData: _oilData, ); } @@ -77,8 +73,7 @@ class _NetworkDropdownFormFieldState extends State { final String? _responseTagName; final String? _labelText; final double _width; - final Duration _flushBarDuration; - final String _passwordKey; + final Duration? _flushBarDuration; bool _accessAllowed = false; int? _dropdownValue; int? _initValue; @@ -93,9 +88,8 @@ class _NetworkDropdownFormFieldState extends State { required String? responseTagName, required String? labelText, required double width, - required Duration flushBarDuration, required OilData oilData, - required String passwordKey, + Duration? flushBarDuration, }) : // _onAuthRequested = onAuthRequested, _allowedGroups = allowedGroups, @@ -107,7 +101,6 @@ class _NetworkDropdownFormFieldState extends State { _width = width, _flushBarDuration = flushBarDuration, _oilData = oilData, - _passwordKey = passwordKey, super(); /// @override @@ -264,8 +257,6 @@ class _NetworkDropdownFormFieldState extends State { networkFieldAuthenticate( context, users, - _passwordKey, - flushbarDuration: _flushBarDuration ).then((AuthResult authResult) { if (authResult.authenticated) { setState(() { @@ -276,8 +267,10 @@ class _NetworkDropdownFormFieldState extends State { }); } FlushbarHelper.createError( - duration: _flushBarDuration, - message: Localized('Editing is not permitted for current user').toString(), + duration: _flushBarDuration ?? Duration( + milliseconds: AppUiSettingsNum.getSetting('flushBarDurationMedium') as int, + ), + message: const Localized('Editing is not permitted for current user').v, ).show(context); _accessAllowed = false; } diff --git a/lib/src/edit_field/network_field_authenticate.dart b/lib/src/edit_field/network_field_authenticate.dart index 9de8f42..d5149c4 100644 --- a/lib/src/edit_field/network_field_authenticate.dart +++ b/lib/src/edit_field/network_field_authenticate.dart @@ -6,18 +6,17 @@ import 'package:hmi_widgets/src/dialogs/auth_dialog.dart'; Future networkFieldAuthenticate( BuildContext context, AppUserStacked users, - String passwordKey, { - Duration flushbarDuration = const Duration(milliseconds: 1000), - } ) { const _debug = true; + final flushBarDuration = Duration( + milliseconds: AppUiSettingsNum.getSetting('flushBarDurationMedium') as int, + ); return Navigator.of(context).push( MaterialPageRoute( builder: (context) => AuthDialog( key: UniqueKey(), currentUser: users.peek, - passwordKey: passwordKey, - flushBarDuration: flushbarDuration, + flushBarDuration: flushBarDuration, ), settings: const RouteSettings(name: "/authDialog"), ), diff --git a/pubspec.yaml b/pubspec.yaml index ad9bc3f..301a02b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dev_dependencies: hmi_core: git: url: https://github.com/a-givertzman/hmi_core.git - ref: Move-to-hmi_core + ref: Localized-refactoring hmi_networking: git: url: https://github.com/a-givertzman/hmi_networking.git From d0e1f949c55b6e75b1fe7982c0b3d1233877bbed Mon Sep 17 00:00:00 2001 From: anton lobanov Date: Wed, 8 Feb 2023 16:13:55 +0300 Subject: [PATCH 15/25] AcDriveWidget default motor icons --- example/pubspec.yaml | 4 ++-- lib/src/process/electrical/drive/ac_drive_widget.dart | 10 ++++++---- pubspec.yaml | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9077d21..9c04762 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,11 +15,11 @@ dependencies: hmi_core: git: url: https://github.com/a-givertzman/hmi_core.git - ref: Localized-refactoring + ref: master hmi_networking: git: url: https://github.com/a-givertzman/hmi_networking.git - ref: Add-to-hmi_networking + ref: master fl_chart: ^0.55.1 another_flushbar: ^1.12.29 diff --git a/lib/src/process/electrical/drive/ac_drive_widget.dart b/lib/src/process/electrical/drive/ac_drive_widget.dart index d7bb30d..1a1844f 100644 --- a/lib/src/process/electrical/drive/ac_drive_widget.dart +++ b/lib/src/process/electrical/drive/ac_drive_widget.dart @@ -9,13 +9,13 @@ class AcDriveWidget extends StatelessWidget { final Stream>? _stream; final String? _caption; final bool _disabled; - final Widget _acMotorIcon; - final Widget _acMotorFailureIcon; + final Widget? _acMotorIcon; + final Widget? _acMotorFailureIcon; /// const AcDriveWidget({ Key? key, - required Widget acMotorIcon, - required Widget acMotorFailureIcon, + Widget? acMotorIcon, + Widget? acMotorFailureIcon, Stream>? stream, String? caption, bool disabled = false, @@ -29,6 +29,8 @@ class AcDriveWidget extends StatelessWidget { /// @override Widget build(BuildContext context) { + final acMotorIcon = _acMotorIcon ?? Image.asset('default asset from lib'); + final acMotorFailureIcon = _acMotorFailureIcon ?? Image.asset('default asset from lib'); final caption = _caption; final stateColors = Theme.of(context).stateColors; return InvalidStatusIndicator( diff --git a/pubspec.yaml b/pubspec.yaml index 301a02b..776f7d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,11 +22,11 @@ dev_dependencies: hmi_core: git: url: https://github.com/a-givertzman/hmi_core.git - ref: Localized-refactoring + ref: master hmi_networking: git: url: https://github.com/a-givertzman/hmi_networking.git - ref: Add-to-hmi_networking + ref: master # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 6fc6db6102006be02b6826dce3fb2d6da858822d Mon Sep 17 00:00:00 2001 From: anton lobanov Date: Wed, 8 Feb 2023 16:27:04 +0300 Subject: [PATCH 16/25] SwlData input files --- lib/src/charts/crane_load_chart/swl_data.dart | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/src/charts/crane_load_chart/swl_data.dart b/lib/src/charts/crane_load_chart/swl_data.dart index 79e50f3..1fd8cc9 100644 --- a/lib/src/charts/crane_load_chart/swl_data.dart +++ b/lib/src/charts/crane_load_chart/swl_data.dart @@ -3,13 +3,24 @@ import 'package:hmi_core/hmi_core.dart'; class SwlData { static const _debug = true; + final TextFile _xCsvFile; + final TextFile _yCsvFile; + final List _swlCsvFiles; final String _assetPath; final int _count; /// + /// xCsvFile load from '$_assetPath/x.csv' + /// yCsvFile load from '$_assetPath/y.csv' SwlData({ + required TextFile xCsvFile, + required TextFile yCsvFile, + required List swlCsvFiles, required String assetPath, required int count, }) : + _xCsvFile = xCsvFile, + _yCsvFile = yCsvFile, + _swlCsvFiles = swlCsvFiles, _assetPath = assetPath, _count = count; /// @@ -25,8 +36,8 @@ class SwlData { }).toList(); } /// - Future> _loadAsset(String assetName) { - return rootBundle.loadString(assetName) + Future> _loadAsset(TextFile textFile) { + return textFile.content .then((value) { final doubleList = _parseStringList( value.replaceAll('\n', ';').replaceAll(',', '.').trim().split(';'), @@ -41,9 +52,9 @@ class SwlData { }); } /// - Future> get x => _loadAsset('$_assetPath/x.csv'); + Future> get x => _loadAsset(_xCsvFile); /// - Future> get y => _loadAsset('$_assetPath/y.csv'); + Future> get y => _loadAsset(_yCsvFile); /// Future>> get swl async { return Future.wait( From 0ae3e8a1ebf110e1422c340136006e1e7a40ef37 Mon Sep 17 00:00:00 2001 From: anton lobanov Date: Wed, 8 Feb 2023 16:40:29 +0300 Subject: [PATCH 17/25] SwlData input files --- lib/src/charts/crane_load_chart/swl_data.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/charts/crane_load_chart/swl_data.dart b/lib/src/charts/crane_load_chart/swl_data.dart index 1fd8cc9..81ae03d 100644 --- a/lib/src/charts/crane_load_chart/swl_data.dart +++ b/lib/src/charts/crane_load_chart/swl_data.dart @@ -1,16 +1,16 @@ -import 'package:flutter/services.dart'; import 'package:hmi_core/hmi_core.dart'; class SwlData { - static const _debug = true; + static final _log = const Log('SwlData')..level = LogLevel.info; final TextFile _xCsvFile; final TextFile _yCsvFile; final List _swlCsvFiles; final String _assetPath; final int _count; /// - /// xCsvFile load from '$_assetPath/x.csv' - /// yCsvFile load from '$_assetPath/y.csv' + /// [xCsvFile] load from '$_assetPath/x.csv' + /// [yCsvFile] load from '$_assetPath/y.csv' + /// [swlCsvFiles] load from '$_assetPath/swl_$i.csv' SwlData({ required TextFile xCsvFile, required TextFile yCsvFile, @@ -30,7 +30,7 @@ class SwlData { final v = double.parse(e); return v; } catch (error) { - log(_debug, 'Ошибка в методе $runtimeType._parseStringList() значение: $e \nошибка: $error'); + _log.error('Ошибка в методе $runtimeType._parseStringList() значение: $e \nошибка: $error'); return 0.0; } }).toList(); @@ -59,7 +59,7 @@ class SwlData { Future>> get swl async { return Future.wait( List.generate(_count, (i) { - return _loadAsset('$_assetPath/swl_$i.csv'); + return _loadAsset(_swlCsvFiles[i]); }) ); } From 95c5152d8dad2146b54b066f90eba0cadc4000ae Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 8 Feb 2023 18:04:20 +0300 Subject: [PATCH 18/25] Added default icons for AcDriveWidget --- {example/assets => assets}/icons/ac_motor.png | Bin .../icons/ac_motor_failure.png | Bin .../configs/app_ui_settings_config.json | 3 +++ example/lib/main.dart | 9 ++++++- example/lib/pages/process/process_page.dart | 2 -- example/pubspec.yaml | 2 +- .../electrical/drive/ac_drive_widget.dart | 23 +++++++++++++----- pubspec.yaml | 3 ++- 8 files changed, 31 insertions(+), 11 deletions(-) rename {example/assets => assets}/icons/ac_motor.png (100%) rename {example/assets => assets}/icons/ac_motor_failure.png (100%) create mode 100644 example/assets/configs/app_ui_settings_config.json diff --git a/example/assets/icons/ac_motor.png b/assets/icons/ac_motor.png similarity index 100% rename from example/assets/icons/ac_motor.png rename to assets/icons/ac_motor.png diff --git a/example/assets/icons/ac_motor_failure.png b/assets/icons/ac_motor_failure.png similarity index 100% rename from example/assets/icons/ac_motor_failure.png rename to assets/icons/ac_motor_failure.png diff --git a/example/assets/configs/app_ui_settings_config.json b/example/assets/configs/app_ui_settings_config.json new file mode 100644 index 0000000..23b6497 --- /dev/null +++ b/example/assets/configs/app_ui_settings_config.json @@ -0,0 +1,3 @@ +{ + "passwordKey": "passwordKey123abc" +} \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index cf39787..4d56c5f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,8 +1,9 @@ import 'package:example/pages/home/home_page.dart'; import 'package:flutter/material.dart'; +import 'package:hmi_core/hmi_core.dart'; import 'package:hmi_networking/hmi_networking.dart'; /// -void main() { +Future main() async { DataSource.initialize({ 'app-user': DataSet>( params: ApiParams( { @@ -16,6 +17,12 @@ void main() { ), ), }); + WidgetsFlutterBinding.ensureInitialized(); + await AppUiSettingsString.initialize( + jsonMap: JsonMap.fromTextFile( + const TextFile.asset('assets/configs/app_ui_settings_config.json'), + ), + ); runApp(const MyApp()); } /// diff --git a/example/lib/pages/process/process_page.dart b/example/lib/pages/process/process_page.dart index 860a160..1717fca 100644 --- a/example/lib/pages/process/process_page.dart +++ b/example/lib/pages/process/process_page.dart @@ -19,8 +19,6 @@ class ProccessPage extends StatelessWidget { stream: getRandomDataPointStream( (random) => random.nextInt(4), ).asBroadcastStream(), - acMotorIcon: Image.asset('assets/icons/ac_motor.png'), - acMotorFailureIcon: Image.asset('assets/icons/ac_motor_failure.png'), ), ], ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9c04762..4cef891 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -34,7 +34,7 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: assets: - - assets/icons/ + - assets/configs/ # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/lib/src/process/electrical/drive/ac_drive_widget.dart b/lib/src/process/electrical/drive/ac_drive_widget.dart index 1a1844f..a36d415 100644 --- a/lib/src/process/electrical/drive/ac_drive_widget.dart +++ b/lib/src/process/electrical/drive/ac_drive_widget.dart @@ -29,8 +29,19 @@ class AcDriveWidget extends StatelessWidget { /// @override Widget build(BuildContext context) { - final acMotorIcon = _acMotorIcon ?? Image.asset('default asset from lib'); - final acMotorFailureIcon = _acMotorFailureIcon ?? Image.asset('default asset from lib'); + final iconsRootDir = 'assets/icons'; + final acMotorIcon = _acMotorIcon ?? Image( + image: AssetImage( + '$iconsRootDir/ac_motor.png', + package: 'hmi_widgets', + ), + ); + final acMotorFailureIcon = _acMotorFailureIcon ?? Image( + image: AssetImage( + '$iconsRootDir/ac_motor_failure.png', + package: 'hmi_widgets', + ), + ); final caption = _caption; final stateColors = Theme.of(context).stateColors; return InvalidStatusIndicator( @@ -47,28 +58,28 @@ class AcDriveWidget extends StatelessWidget { Theme.of(context).stateColors.invalid, BlendMode.srcIn, ), - child: _acMotorIcon, + child: acMotorIcon, ), posOffIcon: ColorFiltered( colorFilter: ColorFilter.mode( Theme.of(context).stateColors.off, BlendMode.srcIn, ), - child: _acMotorIcon, + child: acMotorIcon, ), posOnIcon: ColorFiltered( colorFilter: ColorFilter.mode( Theme.of(context).stateColors.on, BlendMode.srcIn, ), - child: _acMotorIcon, + child: acMotorIcon, ), posTransientIcon: ColorFiltered( colorFilter: ColorFilter.mode( Theme.of(context).stateColors.error, BlendMode.srcIn, ), - child: _acMotorFailureIcon, + child: acMotorFailureIcon, ), ), caption: (caption != null) diff --git a/pubspec.yaml b/pubspec.yaml index 776f7d1..4417ace 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,8 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - + assets: + - assets/icons/ # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg From e2c07b62b7d461b03695288913f33ebc4c31f23b Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 8 Feb 2023 18:07:22 +0300 Subject: [PATCH 19/25] Replaced assetName constructor param with JsonMap in SwlData and OilData --- lib/src/charts/crane_load_chart/swl_data.dart | 13 ++----- .../network_dropdown_field/oil_data.dart | 39 ++++++------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/lib/src/charts/crane_load_chart/swl_data.dart b/lib/src/charts/crane_load_chart/swl_data.dart index 81ae03d..058d41a 100644 --- a/lib/src/charts/crane_load_chart/swl_data.dart +++ b/lib/src/charts/crane_load_chart/swl_data.dart @@ -5,8 +5,6 @@ class SwlData { final TextFile _xCsvFile; final TextFile _yCsvFile; final List _swlCsvFiles; - final String _assetPath; - final int _count; /// /// [xCsvFile] load from '$_assetPath/x.csv' /// [yCsvFile] load from '$_assetPath/y.csv' @@ -15,14 +13,11 @@ class SwlData { required TextFile xCsvFile, required TextFile yCsvFile, required List swlCsvFiles, - required String assetPath, required int count, }) : _xCsvFile = xCsvFile, _yCsvFile = yCsvFile, - _swlCsvFiles = swlCsvFiles, - _assetPath = assetPath, - _count = count; + _swlCsvFiles = swlCsvFiles; /// List _parseStringList(List strings) { return strings.map((e) { @@ -56,11 +51,9 @@ class SwlData { /// Future> get y => _loadAsset(_yCsvFile); /// - Future>> get swl async { + Future>> get swl { return Future.wait( - List.generate(_count, (i) { - return _loadAsset(_swlCsvFiles[i]); - }) + _swlCsvFiles.map((swlFile) => _loadAsset(swlFile)), ); } } diff --git a/lib/src/edit_field/network_dropdown_field/oil_data.dart b/lib/src/edit_field/network_dropdown_field/oil_data.dart index 070289e..6b27018 100644 --- a/lib/src/edit_field/network_dropdown_field/oil_data.dart +++ b/lib/src/edit_field/network_dropdown_field/oil_data.dart @@ -1,48 +1,31 @@ -import 'dart:convert'; - -import 'package:flutter/services.dart'; import 'package:hmi_core/hmi_core.dart'; - /// /// Reads oil names and spec data for [SettingsPage] - HPU /// from assets json file class OilData { static const _debug = true; - final String _assetName; + final JsonMap> _jsonMap; final Map> _data = {}; /// OilData({ - required String assetName, + required JsonMap> jsonMap, }) : - _assetName = assetName; + _jsonMap = jsonMap; /// /// List of names of awailable oil types Future> names() async { if (_data.isEmpty) { - await _loadAsset('$_assetName') - .then((value) => _data.addAll(value)); - } - final namesList = _data.keys.toList(); - log(_debug, '[$OilData.names] namesList: ', namesList); - return Future.value(namesList); - } - /// - Future>> _loadAsset(String assetName) { - return rootBundle.loadString(assetName) - .then((value) { - final jsonResult = json.decode(value) as Map; - return jsonResult.map((key, value) { - return MapEntry( - key, - value as Map, - ); - }); - }) + await _jsonMap.decoded + .then((value) => _data.addAll(value)) .onError((error, stackTrace) { throw Failure.unexpected( - message: 'Ошибка в методе _loadAsset класса $runtimeType:\n$error', + message: 'Ошибка в методе names класса $runtimeType:\n$error', stackTrace: stackTrace, ); }); - } + } + final namesList = _data.keys.toList(); + log(_debug, '[$OilData.names] namesList: ', namesList); + return Future.value(namesList); + } } \ No newline at end of file From b8cec39a997225511bcd141587e8224d70a5f3fb Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Wed, 8 Feb 2023 19:05:28 +0300 Subject: [PATCH 20/25] Added SwlData test --- lib/src/charts/crane_load_chart/swl_data.dart | 3 +- .../swl_data/fake_text_file.dart | 9 +++ .../swl_data/swl_data_test.dart | 61 +++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 test/unit/charts/crane_load_chart/swl_data/fake_text_file.dart create mode 100644 test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart diff --git a/lib/src/charts/crane_load_chart/swl_data.dart b/lib/src/charts/crane_load_chart/swl_data.dart index 058d41a..3f9deb3 100644 --- a/lib/src/charts/crane_load_chart/swl_data.dart +++ b/lib/src/charts/crane_load_chart/swl_data.dart @@ -1,5 +1,5 @@ import 'package:hmi_core/hmi_core.dart'; - +/// class SwlData { static final _log = const Log('SwlData')..level = LogLevel.info; final TextFile _xCsvFile; @@ -13,7 +13,6 @@ class SwlData { required TextFile xCsvFile, required TextFile yCsvFile, required List swlCsvFiles, - required int count, }) : _xCsvFile = xCsvFile, _yCsvFile = yCsvFile, diff --git a/test/unit/charts/crane_load_chart/swl_data/fake_text_file.dart b/test/unit/charts/crane_load_chart/swl_data/fake_text_file.dart new file mode 100644 index 0000000..eb243ac --- /dev/null +++ b/test/unit/charts/crane_load_chart/swl_data/fake_text_file.dart @@ -0,0 +1,9 @@ +import 'package:hmi_core/hmi_core.dart'; +/// +class FakeTextFile implements TextFile { + final String _content; + const FakeTextFile(this._content); + @override + Future get content => Future.value(_content); + +} \ No newline at end of file diff --git a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart new file mode 100644 index 0000000..86168a2 --- /dev/null +++ b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_widgets/src/charts/crane_load_chart/swl_data.dart'; +import 'fake_text_file.dart'; +void main() { + Log.initialize(level: LogLevel.off); + group('SwlData', () { + test('creates normally with valid files', () async { + final swlTestData = [ + { + 'text_x': '1,1;2,7;3,2;4;5\n6,1;7,2;8,3;9,4;10,5', + 'text_y': '6,1;7,2;8,3;9,4;10,5\n1,1;2,7;3,2;4;5', + 'text_swl': [ + '1,1;2,7;3,2;4;5\n6,1;7,2;8,3;9,4;10,5', + '6,1;7,2;8,3;9,4;10,5\n1,1;2,7;3,2;4;5', + '1,1;2,7;3,2;4;5\n6,1;7,2;8,3;9,4;10,5', + ], + 'x': [1.1, 2.7, 3.2, 4.0, 5.0, 6.1, 7.2, 8.3, 9.4, 10.5], + 'y': [6.1, 7.2, 8.3, 9.4, 10.5, 1.1, 2.7, 3.2, 4.0, 5.0], + 'swl': [ + [1.1, 2.7, 3.2, 4.0, 5.0, 6.1, 7.2, 8.3, 9.4, 10.5], + [6.1, 7.2, 8.3, 9.4, 10.5, 1.1, 2.7, 3.2, 4.0, 5.0], + [1.1, 2.7, 3.2, 4.0, 5.0, 6.1, 7.2, 8.3, 9.4, 10.5], + ], + }, + { + 'text_x': '143,214;124.0;-1,235;239,000\n937845,23;39804.345;54654.35;456,34;789,231\n', + 'text_y': '937845,23;39804.345;54654.35;456,34;789,231\n143,214;124.0;-1,235;239,000\n', + 'text_swl': [ + '937845,23;39804.345;54654.35;456,34;789,231\n143,214;124.0;-1,235;239,000\n', + '143,214;124.0;-1,235;239,000\n937845,23;39804.345;54654.35;456,34;789,231\n', + '143,214;124.0;-1,235;239,000\n937845,23;39804.345;54654.35;456,34;789,231\n', + ], + 'x': [143.214, 124.0, -1.235, 239.0, 937845.23, 39804.345, 54654.35, 456.34, 789.231], + 'y': [937845.23, 39804.345, 54654.35, 456.34, 789.231, 143.214, 124.0, -1.235, 239.0], + 'swl': [ + [937845.23, 39804.345, 54654.35, 456.34, 789.231, 143.214, 124.0, -1.235, 239.0], + [143.214, 124.0, -1.235, 239.0, 937845.23, 39804.345, 54654.35, 456.34, 789.231], + [937845.23, 39804.345, 54654.35, 456.34, 789.231, 143.214, 124.0, -1.235, 239.0], + ], + }, + ]; + for (final entry in swlTestData) { + final textX = entry['text_x'] as String; + final textY = entry['text_y'] as String; + final textSwls = entry['text_swl'] as List; + final x = entry['x'] as List; + final y = entry['y'] as List; + final swl = entry['swl'] as List>; + final swlData = SwlData( + xCsvFile: FakeTextFile(textX), + yCsvFile: FakeTextFile(textY), + swlCsvFiles: textSwls.map((textSwl) => FakeTextFile(textSwl)).toList(), + ); + expect(await swlData.x, x); + expect(await swlData.y, y); + expect(await swlData.swl, swl); + } + }); + }); +} \ No newline at end of file From 2f799488afb6b7dae67fed47baf4cf8023619f3a Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Thu, 9 Feb 2023 11:22:33 +0300 Subject: [PATCH 21/25] Fixed SwlData bug with empty strings --- lib/src/charts/crane_load_chart/swl_data.dart | 20 ++++++++++--------- .../swl_data/swl_data_test.dart | 16 +++++++-------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/src/charts/crane_load_chart/swl_data.dart b/lib/src/charts/crane_load_chart/swl_data.dart index 3f9deb3..0f8ca18 100644 --- a/lib/src/charts/crane_load_chart/swl_data.dart +++ b/lib/src/charts/crane_load_chart/swl_data.dart @@ -19,15 +19,17 @@ class SwlData { _swlCsvFiles = swlCsvFiles; /// List _parseStringList(List strings) { - return strings.map((e) { - try { - final v = double.parse(e); - return v; - } catch (error) { - _log.error('Ошибка в методе $runtimeType._parseStringList() значение: $e \nошибка: $error'); - return 0.0; - } - }).toList(); + return strings + .where((string) => string.isNotEmpty) + .map((e) { + try { + final v = double.parse(e); + return v; + } catch (error) { + _log.error('Ошибка в методе $runtimeType._parseStringList() значение: $e \nошибка: $error'); + return 0.0; + } + }).toList(); } /// Future> _loadAsset(TextFile textFile) { diff --git a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart index 86168a2..a65b0ce 100644 --- a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart +++ b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart @@ -8,12 +8,12 @@ void main() { test('creates normally with valid files', () async { final swlTestData = [ { - 'text_x': '1,1;2,7;3,2;4;5\n6,1;7,2;8,3;9,4;10,5', - 'text_y': '6,1;7,2;8,3;9,4;10,5\n1,1;2,7;3,2;4;5', + 'text_x': '1,1;2,7;3,2;4;5\n\n6,1;7,2;8,3;9,4;10,5', + 'text_y': '6,1;7,2;8,3;9,4;10,5\n\n\n1,1;2,7;3,2;4;5', 'text_swl': [ - '1,1;2,7;3,2;4;5\n6,1;7,2;8,3;9,4;10,5', - '6,1;7,2;8,3;9,4;10,5\n1,1;2,7;3,2;4;5', - '1,1;2,7;3,2;4;5\n6,1;7,2;8,3;9,4;10,5', + '1,1;2,7;3,2;4;5\n\n6,1;7,2;8,3;9,4;10,5', + '\n6,1;7,2;8,3;9,4;10,5\n1,1;2,7;3,2;4;5\n', + '1,1;2,7;3,2;4;5\n\n\n6,1;7,2;8,3;9,4;10,5', ], 'x': [1.1, 2.7, 3.2, 4.0, 5.0, 6.1, 7.2, 8.3, 9.4, 10.5], 'y': [6.1, 7.2, 8.3, 9.4, 10.5, 1.1, 2.7, 3.2, 4.0, 5.0], @@ -25,10 +25,10 @@ void main() { }, { 'text_x': '143,214;124.0;-1,235;239,000\n937845,23;39804.345;54654.35;456,34;789,231\n', - 'text_y': '937845,23;39804.345;54654.35;456,34;789,231\n143,214;124.0;-1,235;239,000\n', + 'text_y': '\n937845,23;39804.345;54654.35;456,34;789,231\n143,214;124.0;-1,235;239,000\n', 'text_swl': [ '937845,23;39804.345;54654.35;456,34;789,231\n143,214;124.0;-1,235;239,000\n', - '143,214;124.0;-1,235;239,000\n937845,23;39804.345;54654.35;456,34;789,231\n', + '\n143,214;124.0;-1,235;239,000\n937845,23;39804.345;54654.35;456,34;789,231\n', '143,214;124.0;-1,235;239,000\n937845,23;39804.345;54654.35;456,34;789,231\n', ], 'x': [143.214, 124.0, -1.235, 239.0, 937845.23, 39804.345, 54654.35, 456.34, 789.231], @@ -36,7 +36,7 @@ void main() { 'swl': [ [937845.23, 39804.345, 54654.35, 456.34, 789.231, 143.214, 124.0, -1.235, 239.0], [143.214, 124.0, -1.235, 239.0, 937845.23, 39804.345, 54654.35, 456.34, 789.231], - [937845.23, 39804.345, 54654.35, 456.34, 789.231, 143.214, 124.0, -1.235, 239.0], + [143.214, 124.0, -1.235, 239.0, 937845.23, 39804.345, 54654.35, 456.34, 789.231], ], }, ]; From 6fb1270233984b558b003e52a5874f3b2184cadd Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Thu, 9 Feb 2023 11:57:47 +0300 Subject: [PATCH 22/25] Added OilData test --- .../swl_data/swl_data_test.dart | 2 +- .../oil_data/fake_json_map.dart | 8 +++ .../oil_data/oil_data_names_test.dart | 67 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 test/unit/edit_field/network_dropdown_field/oil_data/fake_json_map.dart create mode 100644 test/unit/edit_field/network_dropdown_field/oil_data/oil_data_names_test.dart diff --git a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart index a65b0ce..1db1ede 100644 --- a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart +++ b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart @@ -6,7 +6,7 @@ void main() { Log.initialize(level: LogLevel.off); group('SwlData', () { test('creates normally with valid files', () async { - final swlTestData = [ + const swlTestData = [ { 'text_x': '1,1;2,7;3,2;4;5\n\n6,1;7,2;8,3;9,4;10,5', 'text_y': '6,1;7,2;8,3;9,4;10,5\n\n\n1,1;2,7;3,2;4;5', diff --git a/test/unit/edit_field/network_dropdown_field/oil_data/fake_json_map.dart b/test/unit/edit_field/network_dropdown_field/oil_data/fake_json_map.dart new file mode 100644 index 0000000..1162faf --- /dev/null +++ b/test/unit/edit_field/network_dropdown_field/oil_data/fake_json_map.dart @@ -0,0 +1,8 @@ +import 'package:hmi_core/hmi_core.dart'; +/// +class FakeJsonMap implements JsonMap { + final Map _decoded; + const FakeJsonMap(this._decoded); + @override + Future> get decoded => Future.value(_decoded); +} \ No newline at end of file diff --git a/test/unit/edit_field/network_dropdown_field/oil_data/oil_data_names_test.dart b/test/unit/edit_field/network_dropdown_field/oil_data/oil_data_names_test.dart new file mode 100644 index 0000000..3eabf85 --- /dev/null +++ b/test/unit/edit_field/network_dropdown_field/oil_data/oil_data_names_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/hmi_core.dart'; +import 'package:hmi_widgets/src/edit_field/network_dropdown_field/oil_data.dart'; + +import 'fake_json_map.dart'; +void main() { + Log.initialize(level: LogLevel.off); + group('OilData', () { + test('creates normally with valid file', () async { + const oilTestData = [ + { + 'json_map': { + 'oil1': { + 'din': 'DIN 12345-6', + 'tempMax': 2, + 'tempMin': 1, + }, + }, + 'names': ['oil1'], + }, + { + 'json_map': { + 'someOil1': { + 'din': 'DIN 78910-1', + 'tempMax': 135, + 'tempMin': 13, + }, + 'fakeOil': { + 'din': 'DIN 23456-7', + 'tempMax': 364, + 'tempMin': 253, + }, + }, + 'names': ['someOil1', 'fakeOil'], + }, + { + 'json_map': { + 'testOil': { + 'din': 'DIN 89101-2', + 'tempMax': 2764, + 'tempMin': 364, + }, + 'unexistingOil': { + 'din': 'DIN 34567-8', + 'tempMax': 587, + 'tempMin': 43, + }, + 'mythicalOil': { + 'din': 'DIN 91012-3', + 'tempMax': 235, + 'tempMin': 123, + }, + }, + 'names': ['testOil', 'unexistingOil', 'mythicalOil'], + }, + ]; + for (final entry in oilTestData) { + final oilJsonMap = entry['json_map'] as Map>; + final names = entry['names'] as List; + final oilData = OilData( + jsonMap: FakeJsonMap(oilJsonMap), + ); + expect(await oilData.names(), names); + } + }); + }); +} \ No newline at end of file From 4600704c2662b9617bd61f52f26e5e75099b59de Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Thu, 9 Feb 2023 12:04:34 +0300 Subject: [PATCH 23/25] Used Log inside OilData --- lib/src/edit_field/network_dropdown_field/oil_data.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/edit_field/network_dropdown_field/oil_data.dart b/lib/src/edit_field/network_dropdown_field/oil_data.dart index 6b27018..93846a0 100644 --- a/lib/src/edit_field/network_dropdown_field/oil_data.dart +++ b/lib/src/edit_field/network_dropdown_field/oil_data.dart @@ -3,7 +3,7 @@ import 'package:hmi_core/hmi_core.dart'; /// Reads oil names and spec data for [SettingsPage] - HPU /// from assets json file class OilData { - static const _debug = true; + static final _log = const Log('OilData')..level = LogLevel.info; final JsonMap> _jsonMap; final Map> _data = {}; /// @@ -25,7 +25,7 @@ class OilData { }); } final namesList = _data.keys.toList(); - log(_debug, '[$OilData.names] namesList: ', namesList); + _log.info('[$OilData.names] namesList: $namesList'); return Future.value(namesList); } } \ No newline at end of file From 2eabfd62c89aca683fff24ea1d7ce7ef374dd265 Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Thu, 9 Feb 2023 12:22:50 +0300 Subject: [PATCH 24/25] SwlData test | compared received lists' lengths --- .../crane_load_chart/swl_data/swl_data_test.dart | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart index 1db1ede..8695991 100644 --- a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart +++ b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart @@ -52,9 +52,17 @@ void main() { yCsvFile: FakeTextFile(textY), swlCsvFiles: textSwls.map((textSwl) => FakeTextFile(textSwl)).toList(), ); - expect(await swlData.x, x); - expect(await swlData.y, y); - expect(await swlData.swl, swl); + final receivedX = await swlData.x; + final receivedY = await swlData.y; + final receivedSwls = await swlData.swl; + expect(receivedX, x); + expect(receivedY, y); + expect(receivedSwls, swl); + for(final receivedSwl in receivedSwls) { + expect(receivedX.length, receivedY.length); + expect(receivedX.length, receivedSwl.length); + expect(receivedY.length, receivedSwl.length); + } } }); }); From 5107d183fa3b6ce546e3760445a69c3433dff203 Mon Sep 17 00:00:00 2001 From: Minyewoo Date: Thu, 9 Feb 2023 15:26:45 +0300 Subject: [PATCH 25/25] Added spaces to SwlData empty strings test case --- lib/src/charts/crane_load_chart/swl_data.dart | 2 +- .../charts/crane_load_chart/swl_data/swl_data_test.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/charts/crane_load_chart/swl_data.dart b/lib/src/charts/crane_load_chart/swl_data.dart index 0f8ca18..bd194ec 100644 --- a/lib/src/charts/crane_load_chart/swl_data.dart +++ b/lib/src/charts/crane_load_chart/swl_data.dart @@ -20,7 +20,7 @@ class SwlData { /// List _parseStringList(List strings) { return strings - .where((string) => string.isNotEmpty) + .where((string) => string.trim().isNotEmpty) .map((e) { try { final v = double.parse(e); diff --git a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart index 8695991..0b56685 100644 --- a/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart +++ b/test/unit/charts/crane_load_chart/swl_data/swl_data_test.dart @@ -8,12 +8,12 @@ void main() { test('creates normally with valid files', () async { const swlTestData = [ { - 'text_x': '1,1;2,7;3,2;4;5\n\n6,1;7,2;8,3;9,4;10,5', + 'text_x': '1,1;2,7;3,2;4;5\n \n6,1;7,2;8,3;9,4;10,5', 'text_y': '6,1;7,2;8,3;9,4;10,5\n\n\n1,1;2,7;3,2;4;5', 'text_swl': [ - '1,1;2,7;3,2;4;5\n\n6,1;7,2;8,3;9,4;10,5', + '1,1;2,7;3,2;4;5\n \n6,1;7,2;8,3;9,4;10,5', '\n6,1;7,2;8,3;9,4;10,5\n1,1;2,7;3,2;4;5\n', - '1,1;2,7;3,2;4;5\n\n\n6,1;7,2;8,3;9,4;10,5', + '1,1;2,7;3,2;4;5\n\n \n6,1;7,2;8,3;9,4;10,5', ], 'x': [1.1, 2.7, 3.2, 4.0, 5.0, 6.1, 7.2, 8.3, 9.4, 10.5], 'y': [6.1, 7.2, 8.3, 9.4, 10.5, 1.1, 2.7, 3.2, 4.0, 5.0],