From bb270a5aa520bdc76e696018bc03850d7356e293 Mon Sep 17 00:00:00 2001 From: Alexandr Date: Sat, 15 Feb 2025 18:17:58 +0300 Subject: [PATCH 1/3] add wheel steering mode change dialog --- .../flows/selected_data_source_scope.dart | 6 + .../blocs/change_wheel_steering_bloc.dart | 67 ++++++++++ .../data_source/blocs/general_data_cubit.dart | 29 +++-- lib/domain/data_source/data_source.dart | 1 + .../models/data_source_parameter_id.dart | 10 ++ .../package/incoming/wheelSteering.dart | 9 ++ .../implementations/wheel_steering.dart | 97 +++++++++++++++ .../models/package_data/package_data.dart | 1 + lib/l10n/arb/app_en.arb | 1 + lib/l10n/arb/app_ru.arb | 1 + lib/presentation/routes/main_router.dart | 6 + .../screens/general/general_screen.dart | 23 +++- .../widgets/change_wheel_steering_dialog.dart | 114 ++++++++++++++++++ .../widgets/wheel_steering_symbol_widget.dart | 43 +++++++ .../widgets/wheel_steering_widget.dart | 110 +++++++++++++++++ 15 files changed, 507 insertions(+), 11 deletions(-) create mode 100644 lib/domain/data_source/blocs/change_wheel_steering_bloc.dart create mode 100644 lib/domain/data_source/models/package/incoming/wheelSteering.dart create mode 100644 lib/domain/data_source/models/package_data/implementations/wheel_steering.dart create mode 100644 lib/presentation/screens/general/widgets/change_wheel_steering_dialog.dart create mode 100644 lib/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart create mode 100644 lib/presentation/screens/general/widgets/wheel_steering_widget.dart diff --git a/lib/app/scopes/flows/selected_data_source_scope.dart b/lib/app/scopes/flows/selected_data_source_scope.dart index 038531c..2ddd430 100644 --- a/lib/app/scopes/flows/selected_data_source_scope.dart +++ b/lib/app/scopes/flows/selected_data_source_scope.dart @@ -198,6 +198,12 @@ class SelectedDataSourceScope extends AutoRouter { generalDataCubit: context.read(), ), ), + BlocProvider( + create: (context) => ChangeWheelSteeringBloc( + dataSource: context.read(), + generalDataCubit: context.read(), + ), + ), BlocProvider( create: (context) => LaunchAppCubit(appsService: context.read()), diff --git a/lib/domain/data_source/blocs/change_wheel_steering_bloc.dart b/lib/domain/data_source/blocs/change_wheel_steering_bloc.dart new file mode 100644 index 0000000..c75e364 --- /dev/null +++ b/lib/domain/data_source/blocs/change_wheel_steering_bloc.dart @@ -0,0 +1,67 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package/outgoing/outgoing_data_source_packages.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; +import 'package:re_seedwork/re_seedwork.dart'; + +part 'change_wheel_steering_bloc.freezed.dart'; + +@freezed +class ChangeWheelSteeringEvent extends EffectEvent with _$ChangeWheelSteeringEvent { + const factory ChangeWheelSteeringEvent.change(WheelSteering newValue) = _Change; +} + +typedef ChangeWheelSteeringState = AsyncData; + +class ChangeWheelSteeringBloc extends Bloc + with BlocEventHandlerMixin { + ChangeWheelSteeringBloc({ + required this.dataSource, + required this.generalDataCubit, + }) : super(const ChangeWheelSteeringState.initial(WheelSteering.free)) { + on<_Change>(_onChange); + } + + @visibleForTesting + static const List kParameterIds = [ + DataSourceParameterId.wheelSteering(), + ]; + + @protected + final DataSource dataSource; + + @protected + final GeneralDataCubit generalDataCubit; + + Future _onChange( + _Change event, + Emitter> emit, + ) async { + emit(AsyncData.loading(event.newValue)); + + try { + final future = generalDataCubit.stream + .firstWhere((element) => element.wheelSteering == event.newValue) + .timeout(const Duration(seconds: 2)); + for (final parameterId in kParameterIds) { + final res = await dataSource.sendPackage( + OutgoingSetValuePackage( + parameterId: parameterId, + setValueBody: SetInt8Body(value: event.newValue.id), + ), + ); + if (res.isError) { + return emit(AsyncData.failure(generalDataCubit.state.wheelSteering)); + } + } + await future; + + emit(AsyncData.success(event.newValue)); + } on Object catch (_) { + emit(AsyncData.failure(generalDataCubit.state.wheelSteering)); + + rethrow; + } + } +} diff --git a/lib/domain/data_source/blocs/general_data_cubit.dart b/lib/domain/data_source/blocs/general_data_cubit.dart index 4673222..9288e33 100644 --- a/lib/domain/data_source/blocs/general_data_cubit.dart +++ b/lib/domain/data_source/blocs/general_data_cubit.dart @@ -5,6 +5,7 @@ import 'package:pixel_app_flutter/domain/app/app.dart'; import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package/incoming/battery_percent.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package/incoming/incoming_data_source_packages.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package/incoming/wheelSteering.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; import 'package:re_seedwork/re_seedwork.dart'; @@ -15,6 +16,7 @@ typedef _IntValueModifier = ( extension _SequenceExt on Sequence { static int avgFold(int a, int b) => a + b; + static int maxFold(int a, int b) => a > b ? a : b; static _IntValueModifier avg = @@ -50,6 +52,7 @@ final class GeneralDataState with EquatableMixin { required this.odometer, required this.speed, required this.gear, + required this.wheelSteering, }); GeneralDataState.initial({ @@ -70,7 +73,8 @@ final class GeneralDataState with EquatableMixin { gear = Sequence.fill( hardwareCount.motors, MotorGear.unknown, - ); + ), + wheelSteering = WheelSteering.free; factory GeneralDataState.fromMap(Map map) { return GeneralDataState( @@ -88,6 +92,7 @@ final class GeneralDataState with EquatableMixin { gear: Sequence.fromIterable( map.tryParseAndMapList('gear', MotorGear.fromId), ), + wheelSteering: map.parseAndMap('wheelSteering', WheelSteering.fromId), ); } @@ -97,6 +102,7 @@ final class GeneralDataState with EquatableMixin { final Sequence speed; final Sequence gear; final Sequence batteryPercent; + final WheelSteering wheelSteering; // TODO(Radomir): hardcoded temporarily because there is only one battery // at the moment, and the merged value(mean) is irrelevant @@ -120,13 +126,8 @@ final class GeneralDataState with EquatableMixin { } @override - List get props => [ - power, - batteryPercent, - odometer, - speed, - gear, - ]; + List get props => + [power, batteryPercent, odometer, speed, gear, wheelSteering]; GeneralDataState copyWith({ Sequence? power, @@ -134,6 +135,7 @@ final class GeneralDataState with EquatableMixin { IntWithStatus? odometer, Sequence? speed, Sequence? gear, + WheelSteering? wheelSteering, }) { return GeneralDataState( hardwareCount: hardwareCount, @@ -142,6 +144,7 @@ final class GeneralDataState with EquatableMixin { odometer: odometer ?? this.odometer, speed: speed ?? this.speed, gear: gear ?? this.gear, + wheelSteering: wheelSteering ?? this.wheelSteering, ); } @@ -153,6 +156,7 @@ final class GeneralDataState with EquatableMixin { 'odometer': odometer.toMap(), 'speed': [for (final e in speed) e.toMap()], 'gear': [for (final e in gear) e.id], + 'wheelSteering': wheelSteering.id, }; } } @@ -216,6 +220,14 @@ class GeneralDataCubit extends Cubit with ConsumerBlocMixin { odometer: IntWithStatus.fromBytesConvertible(model, km), ), ); + }) + ..voidOnModel((model) { + emit( + state.copyWith( + wheelSteering: WheelSteering.fromId(model.value) , + ), + ); }); }); } @@ -234,6 +246,7 @@ class GeneralDataCubit extends Cubit with ConsumerBlocMixin { const DataSourceParameterId.batteryPercent2(), const DataSourceParameterId.batteryPower1(), const DataSourceParameterId.batteryPower2(), + const DataSourceParameterId.wheelSteering(), }; @protected diff --git a/lib/domain/data_source/data_source.dart b/lib/domain/data_source/data_source.dart index 07189e1..f9f0b5c 100644 --- a/lib/domain/data_source/data_source.dart +++ b/lib/domain/data_source/data_source.dart @@ -1,6 +1,7 @@ // blocs export 'blocs/battery_data_cubit.dart'; export 'blocs/change_gear_bloc.dart'; +export 'blocs/change_wheel_steering_bloc.dart'; export 'blocs/data_source_authorization_cubit.dart'; export 'blocs/data_source_connect_bloc.dart'; export 'blocs/data_source_connection_status_cubit.dart'; diff --git a/lib/domain/data_source/models/data_source_parameter_id.dart b/lib/domain/data_source/models/data_source_parameter_id.dart index ba86c69..4e6c335 100644 --- a/lib/domain/data_source/models/data_source_parameter_id.dart +++ b/lib/domain/data_source/models/data_source_parameter_id.dart @@ -136,6 +136,9 @@ abstract class DataSourceParameterId { const factory DataSourceParameterId.transmission4() = Transmission4ParameterId; + const factory DataSourceParameterId.wheelSteering() = + WheelSteeringParameterId; + const factory DataSourceParameterId.motorTemperature1() = MotorTemperature1ParameterId; const factory DataSourceParameterId.motorTemperature2() = @@ -269,6 +272,8 @@ abstract class DataSourceParameterId { bool get isTransmission3 => this is Transmission3ParameterId; bool get isTransmission4 => this is Transmission4ParameterId; + bool get isWheelSteering => this is WheelSteeringParameterId; + bool get isMotorTemperature1 => this is MotorTemperature1ParameterId; bool get isMotorTemperature2 => this is MotorTemperature2ParameterId; bool get isMotorTemperature3 => this is MotorTemperature3ParameterId; @@ -372,6 +377,7 @@ abstract class DataSourceParameterId { DataSourceParameterId.transmission2(), DataSourceParameterId.transmission3(), DataSourceParameterId.transmission4(), + DataSourceParameterId.wheelSteering(), DataSourceParameterId.motorTemperature1(), DataSourceParameterId.motorTemperature2(), DataSourceParameterId.motorTemperature3(), @@ -671,6 +677,10 @@ class Transmission4ParameterId extends DataSourceParameterId { const Transmission4ParameterId() : super(0x0137); } +class WheelSteeringParameterId extends DataSourceParameterId { + const WheelSteeringParameterId() : super(0x01A4); +} + class MotorTemperature1ParameterId extends DataSourceParameterId { const MotorTemperature1ParameterId() : super(0x011A); } diff --git a/lib/domain/data_source/models/package/incoming/wheelSteering.dart b/lib/domain/data_source/models/package/incoming/wheelSteering.dart new file mode 100644 index 0000000..659f861 --- /dev/null +++ b/lib/domain/data_source/models/package/incoming/wheelSteering.dart @@ -0,0 +1,9 @@ +import 'package:pixel_app_flutter/domain/data_source/models/package/data_source_incoming_package.dart'; + +class WheelSteeringIncomingDataSourcePackage + extends Uint8WithStatusIncomingDataSourcePackage { + WheelSteeringIncomingDataSourcePackage(super.source); + + @override + bool get validParameterId => parameterId.isWheelSteering; +} diff --git a/lib/domain/data_source/models/package_data/implementations/wheel_steering.dart b/lib/domain/data_source/models/package_data/implementations/wheel_steering.dart new file mode 100644 index 0000000..dd61a9f --- /dev/null +++ b/lib/domain/data_source/models/package_data/implementations/wheel_steering.dart @@ -0,0 +1,97 @@ +import 'dart:math'; + +import 'package:pixel_app_flutter/domain/data_source/extensions/int.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package_data/wrappers/bytes_convertible_with_status.dart'; + +enum WheelSteering { + free(0x00), + align(0x01), + fastTurn(0x02), + diagonal(0x03), + blocked(0x04); + + const WheelSteering(this.id); + final int id; + + static WheelSteering fromId(int id) { + return WheelSteering.values.firstWhere( + (element) => element.id == id, + orElse: () => WheelSteering.free, + ); + } + + R when({ + required R Function() free, + required R Function() align, + required R Function() fastTurn, + required R Function() diagonal, + required R Function() blocked, + }) { + return switch (this) { + WheelSteering.free => free(), + WheelSteering.align => align(), + WheelSteering.fastTurn => fastTurn(), + WheelSteering.diagonal => diagonal(), + WheelSteering.blocked => blocked(), + }; + } + + static WheelSteering get random => values[Random().nextInt(values.length)]; +} + +class WheelSteeringPacket extends IntBytesConvertibleWithStatus { + WheelSteeringPacket({ + required this.wheelSteering, + required super.status, + }) : super( + value: _toUint8( + wheelSteering: wheelSteering, + ), + ); + + factory WheelSteeringPacket.unknown() => WheelSteeringPacket( + wheelSteering: WheelSteering.free, + status: PeriodicValueStatus.normal, + ); + + WheelSteeringPacket.fromId({ + required this.wheelSteering, + required super.id, + }) : super.fromId( + value: _toUint8( + wheelSteering: wheelSteering, + ), + ); + + factory WheelSteeringPacket.builder(int functionId, int value) { + final bytes = value.toBytesUint8; + + return WheelSteeringPacket.fromId( + id: functionId, + wheelSteering: WheelSteering.fromId(bytes[0]), + ); + } + + final WheelSteering wheelSteering; + + static int _toUint8({ + required WheelSteering wheelSteering, + }) { + return [ + wheelSteering.id, + ].toIntFromUint8; + } + + @override + List get props => [ + ...super.props, + wheelSteering, + ]; + + static Uint8WithStatusBytesConverter get converter => + const Uint8WithStatusBytesConverter(WheelSteeringPacket.builder); + + @override + BytesConverter get bytesConverter => converter; +} diff --git a/lib/domain/data_source/models/package_data/package_data.dart b/lib/domain/data_source/models/package_data/package_data.dart index ac11532..960c814 100644 --- a/lib/domain/data_source/models/package_data/package_data.dart +++ b/lib/domain/data_source/models/package_data/package_data.dart @@ -25,6 +25,7 @@ export 'implementations/two_uint16_with_status_body.dart'; export 'implementations/uint16_with_status_body.dart'; export 'implementations/uint32_with_status_body.dart'; export 'implementations/motor_gear_and_roll.dart'; +export 'implementations/wheel_steering.dart'; export 'implementations/int16_with_status_body.dart'; export 'implementations/set_int8_body.dart'; export 'implementations/set_uint8_body.dart'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 906b893..afbb156 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -290,6 +290,7 @@ "boostGearShort": "B", "unknownGearShort": "U", "errorSwitchingGearMessage": "Error switching the gear", + "errorChangeWheelSteeringMessage": "Error switching wheels steering mode", "stopBeforeSwitchingGearMessage": "To switch the gear, you must first stop", "reverseMotorRollDirectionShort": "R", "unknownMotorRollDirectionShort": "U", diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 153e4a0..30131b3 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -290,6 +290,7 @@ "boostGearShort": "B", "unknownGearShort": "U", "errorSwitchingGearMessage": "Ошибка переключения передачи", + "errorChangeWheelSteeringMessage": "Ошибка переключения режима поворота", "stopBeforeSwitchingGearMessage": "Для переключения передачи нужно сначала остановиться", "reverseMotorRollDirectionShort": "R", "unknownMotorRollDirectionShort": "U", diff --git a/lib/presentation/routes/main_router.dart b/lib/presentation/routes/main_router.dart index 39664c4..357b851 100644 --- a/lib/presentation/routes/main_router.dart +++ b/lib/presentation/routes/main_router.dart @@ -31,6 +31,7 @@ import 'package:pixel_app_flutter/presentation/screens/developer_tools/widgets/i import 'package:pixel_app_flutter/presentation/screens/developer_tools/widgets/slider_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/general/general_screen.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/change_gear_dialog.dart'; +import 'package:pixel_app_flutter/presentation/screens/general/widgets/change_wheel_steering_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/led_panel_switcher_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/suspension_control_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/home/home_screen.dart'; @@ -88,6 +89,11 @@ class MainRouter extends RootStackRouter { page: ChangeGearDialogRoute.page, customRouteBuilder: dialogRouteBuilder, ), + CustomRoute( + path: 'change-wheel-steering-dialog', + page: ChangeWheelSteeringDialogRoute.page, + customRouteBuilder: dialogRouteBuilder, + ), ], ), // diff --git a/lib/presentation/screens/general/general_screen.dart b/lib/presentation/screens/general/general_screen.dart index 4233c20..c0eee86 100644 --- a/lib/presentation/screens/general/general_screen.dart +++ b/lib/presentation/screens/general/general_screen.dart @@ -10,6 +10,7 @@ import 'package:pixel_app_flutter/presentation/screens/general/widgets/light_sta import 'package:pixel_app_flutter/presentation/screens/general/widgets/overlay_data_sender.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/suspension_control_button.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/user_defined_buttons_end_drawer.dart'; +import 'package:pixel_app_flutter/presentation/screens/general/widgets/wheel_steering_widget.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/wipers_switcher_button.dart'; import 'package:pixel_app_flutter/presentation/widgets/app/organisms/screen_data.dart'; import 'package:pixel_app_flutter/presentation/widgets/common/atoms/responsive_padding.dart'; @@ -92,7 +93,12 @@ class TabletGeneralScreenBody extends StatelessWidget { screenFlexRange: (600, 700), valueClampRange: (420, 460), ), - child: GearWidget(screenSize: size), + child: Row( + children: [ + GearWidget(screenSize: size), + WheelSteeringWidget(screenSize: size), + ], + ), ), const Positioned( right: 0, @@ -125,7 +131,12 @@ class HandsetGeneralScreenBody extends StatelessWidget { if (landscape) const StatisticWidget() else - GearWidget(screenSize: size), + Column( + children: [ + GearWidget(screenSize: size), + WheelSteeringWidget(screenSize: size), + ], + ), ], ), ), @@ -139,7 +150,13 @@ class HandsetGeneralScreenBody extends StatelessWidget { const SizedBox(height: 16), const SuspensionControlButton(), ] else ...[ - GearWidget(screenSize: size), + Row( + children: [ + GearWidget(screenSize: size), + const SizedBox(width: 16), + WheelSteeringWidget(screenSize: size), + ], + ), const SizedBox(height: 16), const Row( children: [ diff --git a/lib/presentation/screens/general/widgets/change_wheel_steering_dialog.dart b/lib/presentation/screens/general/widgets/change_wheel_steering_dialog.dart new file mode 100644 index 0000000..ca31088 --- /dev/null +++ b/lib/presentation/screens/general/widgets/change_wheel_steering_dialog.dart @@ -0,0 +1,114 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; +import 'package:pixel_app_flutter/presentation/app/colors.dart'; +import 'package:pixel_app_flutter/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart'; +import 'package:pixel_app_flutter/presentation/widgets/app/organisms/screen_data.dart'; +import 'package:re_seedwork/re_seedwork.dart'; + +@RoutePage(name: 'ChangeWheelSteeringDialogRoute') +class ChangeWheelSteeringDialog extends StatefulWidget { + const ChangeWheelSteeringDialog({super.key, required this.padding}); + + final (double? left, double? top, double? right, double? bottom) padding; + + @override + State createState() => + _ChangeWheelSteeringDialogState(); +} + +class _ChangeWheelSteeringDialogState extends State { + @protected + static const kBorderRadius = BorderRadius.all(Radius.circular(10)); + + @protected + static const kDialogActiveTimeSecs = 2; + + bool isInit = true; + + @override + void initState() { + super.initState(); + Future.delayed(const Duration(seconds: kDialogActiveTimeSecs)).then( + (value) { + if (!mounted) return; + context.router.maybePop(); + }, + ); + } + + @override + void didChangeDependencies() { + if (!isInit && mounted) { + context.router.maybePop(); + } + isInit = false; + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + final size = Screen.of(context).size; + + return Stack( + children: [ + Positioned( + left: widget.padding.$1, + top: widget.padding.$2, + right: widget.padding.$3, + bottom: widget.padding.$4, + child: Material( + borderRadius: kBorderRadius, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: kBorderRadius, + color: context.colors.primaryAccent.withAlpha(50), + border: Border.all( + color: context.colors.primary, + width: 2, + ), + ), + child: IntrinsicWidth( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final value in WheelSteering.values) + InkWell( + onTap: () { + context.router.maybePop(value); + }, + borderRadius: kBorderRadius, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: WheelSteeringSymbolWidget( + value: value.index, + screenSize: size, + values: [ + 'free', + 'align', + 'turn', + 'diagonal', + 'blocked', + ], // TODO(Alexandr): replace with icons + ), + ), + ), + ] + .divideBy( + Divider( + thickness: 1, + indent: 6, + endIndent: 6, + color: context.colors.text, + ), + ) + .toList(), + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart b/lib/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart new file mode 100644 index 0000000..01bd406 --- /dev/null +++ b/lib/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart @@ -0,0 +1,43 @@ +import 'package:flutter/widgets.dart'; +import 'package:pixel_app_flutter/presentation/app/colors.dart'; + +class WheelSteeringSymbolWidget extends StatelessWidget { + const WheelSteeringSymbolWidget({ + required this.value, + required this.values, + required this.screenSize, + this.inactive = false, + super.key, + }); + + @protected + final int value; + + @protected + final Size screenSize; + + @protected + final bool inactive; + + @protected + final List values; + + @protected + static const kTextStyle = TextStyle( + height: 1.2, + fontSize: 32, + fontStyle: FontStyle.normal, + fontWeight: FontWeight.w700, + fontFeatures: [FontFeature.tabularFigures()], + ); + + @override + Widget build(BuildContext context) { + return Text( + values.elementAt(value) ?? '', + style: kTextStyle.copyWith( + color: inactive ? context.colors.disabled : context.colors.text, + ), + ); + } +} diff --git a/lib/presentation/screens/general/widgets/wheel_steering_widget.dart b/lib/presentation/screens/general/widgets/wheel_steering_widget.dart new file mode 100644 index 0000000..d59fb14 --- /dev/null +++ b/lib/presentation/screens/general/widgets/wheel_steering_widget.dart @@ -0,0 +1,110 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; +import 'package:pixel_app_flutter/l10n/l10n.dart'; +import 'package:pixel_app_flutter/presentation/routes/main_router.dart'; +import 'package:pixel_app_flutter/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart'; +import 'package:pixel_app_flutter/presentation/widgets/app/organisms/screen_data.dart'; +import 'package:re_widgets/re_widgets.dart'; + +class WheelSteeringWidget extends StatelessWidget { + const WheelSteeringWidget({ + super.key, + required this.screenSize, + }); + + @protected + final Size screenSize; + + @protected + static const kHorizontalPadding = 10; + + @override + Widget build(BuildContext context) { + return BlocSelector( + selector: (state) => state.wheelSteering, + builder: (context, value) { + return BlocConsumer( + listenWhen: (previous, current) => current.isFailure, + listener: (context, state) { + context.showSnackBar(context.l10n.errorChangeWheelSteeringMessage); + }, + builder: (context, settingGearState) { + return InkWell( + borderRadius: const BorderRadius.all(Radius.circular(10)), + onTap: settingGearState.isLoading + ? null + : () async { + final s = Screen.of(context, watch: false); + final rect = context.globalPaintBounds; + final padding = s.orientation == Orientation.portrait && + s.type == ScreenType.handset + ? ( + null, + rect?.top ?? 0, + s.size.width - + (rect?.left ?? 0) + + kHorizontalPadding, + null + ) + : ( + (rect?.left ?? 0) + + (rect?.width ?? 0) + + kHorizontalPadding, + rect?.top ?? 0, + null, + null, + ); + + final wheelSteeringValue = + await context.router.push( + ChangeWheelSteeringDialogRoute( + padding: padding, + ), + ); + + if (wheelSteeringValue == null || !context.mounted) { + return; + } + + context.read().add( + ChangeWheelSteeringEvent.change(wheelSteeringValue), + ); + }, + child: WheelSteeringSymbolWidget( + value: settingGearState.maybeWhen( + orElse: (payload) => value.index, + loading: (payload) => payload.index, + ), + values: const [ + 'free', // TODO(Alexandr): replace with icons + 'align', + 'turn', + 'diagonal', + 'blocked', + ], + inactive: settingGearState.isLoading, + screenSize: screenSize, + ), + ); + }, + ); + }, + ); + } +} + +extension on BuildContext { + Rect? get globalPaintBounds { + final renderObject = findRenderObject(); + final translation = renderObject?.getTransformTo(null).getTranslation(); + if (translation != null && renderObject?.paintBounds != null) { + final offset = Offset(translation.x, translation.y); + return renderObject!.paintBounds.shift(offset); + } else { + return null; + } + } +} From de2821446f053b89dbce4879ba61123bac23e3b2 Mon Sep 17 00:00:00 2001 From: Radomir Epur Date: Mon, 17 Feb 2025 12:54:36 +0300 Subject: [PATCH 2/3] feat: steering rack control(closes #99) * refactor: move MotorGear and MotorRollDirection models to separate files --- .../flows/selected_data_source_scope.dart | 5 +- .../data_source/demo_data_source.dart | 73 ++++++---- .../blocs/change_wheel_steering_bloc.dart | 67 --------- .../data_source/blocs/general_data_cubit.dart | 29 ++-- .../blocs/steering_rack_control_bloc.dart | 127 ++++++++++++++++++ .../blocs/suspension_control_bloc.dart | 25 +--- lib/domain/data_source/data_source.dart | 5 +- lib/domain/data_source/extensions/stream.dart | 15 +++ .../models/data_source_parameter_id.dart | 11 +- lib/domain/data_source/models/motor_gear.dart | 40 ++++++ .../models/motor_roll_direction.dart | 35 +++++ .../package/data_source_incoming_package.dart | 2 + .../incoming_data_source_packages.dart | 1 + .../package/incoming/steering_rack.dart | 9 ++ .../package/incoming/wheelSteering.dart | 9 -- .../models/package_data/function_id.dart | 3 + .../implementations/motor_gear_and_roll.dart | 76 +---------- .../data_source/models/steering_rack.dart | 37 +++++ lib/l10n/arb/app_en.arb | 8 ++ lib/l10n/arb/app_ru.arb | 8 ++ lib/presentation/app/extensions.dart | 1 + lib/presentation/routes/main_router.dart | 8 +- .../routes/subroutes/home_route.dart | 5 + .../screens/general/general_screen.dart | 55 ++++---- .../screens/general/widgets/car_widget.dart | 7 + .../general/widgets/change_gear_dialog.dart | 2 +- .../widgets/change_wheel_steering_dialog.dart | 114 ---------------- .../general/widgets/gear_symbol_widget.dart | 2 +- .../screens/general/widgets/gear_widget.dart | 1 - .../widgets/steering_rack_control_button.dart | 44 ++++++ .../widgets/steering_rack_control_dialog.dart | 120 +++++++++++++++++ .../widgets/wheel_steering_symbol_widget.dart | 43 ------ .../widgets/wheel_steering_widget.dart | 110 --------------- 33 files changed, 563 insertions(+), 534 deletions(-) delete mode 100644 lib/domain/data_source/blocs/change_wheel_steering_bloc.dart create mode 100644 lib/domain/data_source/blocs/steering_rack_control_bloc.dart create mode 100644 lib/domain/data_source/extensions/stream.dart create mode 100644 lib/domain/data_source/models/motor_gear.dart create mode 100644 lib/domain/data_source/models/motor_roll_direction.dart create mode 100644 lib/domain/data_source/models/package/incoming/steering_rack.dart delete mode 100644 lib/domain/data_source/models/package/incoming/wheelSteering.dart create mode 100644 lib/domain/data_source/models/steering_rack.dart delete mode 100644 lib/presentation/screens/general/widgets/change_wheel_steering_dialog.dart create mode 100644 lib/presentation/screens/general/widgets/steering_rack_control_button.dart create mode 100644 lib/presentation/screens/general/widgets/steering_rack_control_dialog.dart delete mode 100644 lib/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart delete mode 100644 lib/presentation/screens/general/widgets/wheel_steering_widget.dart diff --git a/lib/app/scopes/flows/selected_data_source_scope.dart b/lib/app/scopes/flows/selected_data_source_scope.dart index 2ddd430..c257085 100644 --- a/lib/app/scopes/flows/selected_data_source_scope.dart +++ b/lib/app/scopes/flows/selected_data_source_scope.dart @@ -199,10 +199,9 @@ class SelectedDataSourceScope extends AutoRouter { ), ), BlocProvider( - create: (context) => ChangeWheelSteeringBloc( + create: (context) => SteeringRackControlBloc( dataSource: context.read(), - generalDataCubit: context.read(), - ), + )..add(const SteeringRackControlEvent.get()), ), BlocProvider( create: (context) => diff --git a/lib/data/services/data_source/demo_data_source.dart b/lib/data/services/data_source/demo_data_source.dart index 0ab4a68..e689622 100644 --- a/lib/data/services/data_source/demo_data_source.dart +++ b/lib/data/services/data_source/demo_data_source.dart @@ -592,24 +592,16 @@ class DemoDataSource extends DataSource }, unavailableForSubscriptionIds: {}, respondCallback: (id, version, manager, [package]) async { - final data = (package?.data).checkNotNull('Package data'); - final requestType = data.first; - assert( - [ - FunctionId.requestValue.value, - FunctionId.setValueWithParam.value, - ].contains(requestType), - 'Supported only "set" and "get" request types', - ); + final functionId = package.functionId + ..assertIsRequestValueOrSetValue(); await manager.updateCallback( id, SetUint8ResultBody( success: !generateRandomErrors() || randomBool, - value: (generateRandomErrors() || - requestType == FunctionId.requestValue.value) + value: (generateRandomErrors() || functionId.isRequestValue) ? SuspensionMode.random.id - : package?.data.last ?? 0, + : package.dataNotNull.last, ), version, ); @@ -619,28 +611,43 @@ class DemoDataSource extends DataSource ), MainEcuMockResponseWrapper( ids: { - const DataSourceParameterId.suspensionValue(), + const DataSourceParameterId.steeringRack(), }, unavailableForSubscriptionIds: {}, respondCallback: (id, version, manager, [package]) async { - final data = (package?.data).checkNotNull('Package data'); - final requestType = data.first; - assert( - [ - FunctionId.requestValue.value, - FunctionId.setValueWithParam.value, - ].contains(requestType), - 'Supported only "set" and "get" request types', + final functionId = package.functionId + ..assertIsRequestValueOrSetValue(); + + await manager.updateCallback( + id, + SetUint8ResultBody( + success: !generateRandomErrors() || randomBool, + value: (generateRandomErrors() || functionId.isRequestValue) + ? SteeringRack.random.id + : package.dataNotNull.last, + ), + version, ); + return const Result.value(null); + }, + ), + MainEcuMockResponseWrapper( + ids: { + const DataSourceParameterId.suspensionValue(), + }, + unavailableForSubscriptionIds: {}, + respondCallback: (id, version, manager, [package]) async { + final functionId = package.functionId + ..assertIsRequestValueOrSetValue(); + await manager.updateCallback( id, SetUint8ResultBody( success: !generateRandomErrors() || randomBool, - value: (generateRandomErrors() || - requestType == FunctionId.requestValue.value) + value: (generateRandomErrors() || functionId.isRequestValue) ? Random().nextInt(SuspensionMode.kMaxManualValue) - : package?.data.last ?? 0, + : package.dataNotNull.last, ), version, ); @@ -781,6 +788,24 @@ extension on int { bool get toBool => this == 255; } +extension on DataSourceOutgoingPackage? { + List get dataNotNull => checkNotNull('Package').data; + + FunctionId get functionId => FunctionId.fromValue(dataNotNull.first); +} + +extension on FunctionId { + void assertIsRequestValueOrSetValue() { + assert( + [ + FunctionId.requestValue, + FunctionId.setValueWithParam, + ].contains(this), + 'Supported only "set" and "get" request types', + ); + } +} + @visibleForTesting final class MainEcuMockManager { const MainEcuMockManager({ diff --git a/lib/domain/data_source/blocs/change_wheel_steering_bloc.dart b/lib/domain/data_source/blocs/change_wheel_steering_bloc.dart deleted file mode 100644 index c75e364..0000000 --- a/lib/domain/data_source/blocs/change_wheel_steering_bloc.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package/outgoing/outgoing_data_source_packages.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; -import 'package:re_seedwork/re_seedwork.dart'; - -part 'change_wheel_steering_bloc.freezed.dart'; - -@freezed -class ChangeWheelSteeringEvent extends EffectEvent with _$ChangeWheelSteeringEvent { - const factory ChangeWheelSteeringEvent.change(WheelSteering newValue) = _Change; -} - -typedef ChangeWheelSteeringState = AsyncData; - -class ChangeWheelSteeringBloc extends Bloc - with BlocEventHandlerMixin { - ChangeWheelSteeringBloc({ - required this.dataSource, - required this.generalDataCubit, - }) : super(const ChangeWheelSteeringState.initial(WheelSteering.free)) { - on<_Change>(_onChange); - } - - @visibleForTesting - static const List kParameterIds = [ - DataSourceParameterId.wheelSteering(), - ]; - - @protected - final DataSource dataSource; - - @protected - final GeneralDataCubit generalDataCubit; - - Future _onChange( - _Change event, - Emitter> emit, - ) async { - emit(AsyncData.loading(event.newValue)); - - try { - final future = generalDataCubit.stream - .firstWhere((element) => element.wheelSteering == event.newValue) - .timeout(const Duration(seconds: 2)); - for (final parameterId in kParameterIds) { - final res = await dataSource.sendPackage( - OutgoingSetValuePackage( - parameterId: parameterId, - setValueBody: SetInt8Body(value: event.newValue.id), - ), - ); - if (res.isError) { - return emit(AsyncData.failure(generalDataCubit.state.wheelSteering)); - } - } - await future; - - emit(AsyncData.success(event.newValue)); - } on Object catch (_) { - emit(AsyncData.failure(generalDataCubit.state.wheelSteering)); - - rethrow; - } - } -} diff --git a/lib/domain/data_source/blocs/general_data_cubit.dart b/lib/domain/data_source/blocs/general_data_cubit.dart index 9288e33..4673222 100644 --- a/lib/domain/data_source/blocs/general_data_cubit.dart +++ b/lib/domain/data_source/blocs/general_data_cubit.dart @@ -5,7 +5,6 @@ import 'package:pixel_app_flutter/domain/app/app.dart'; import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package/incoming/battery_percent.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package/incoming/incoming_data_source_packages.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package/incoming/wheelSteering.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; import 'package:re_seedwork/re_seedwork.dart'; @@ -16,7 +15,6 @@ typedef _IntValueModifier = ( extension _SequenceExt on Sequence { static int avgFold(int a, int b) => a + b; - static int maxFold(int a, int b) => a > b ? a : b; static _IntValueModifier avg = @@ -52,7 +50,6 @@ final class GeneralDataState with EquatableMixin { required this.odometer, required this.speed, required this.gear, - required this.wheelSteering, }); GeneralDataState.initial({ @@ -73,8 +70,7 @@ final class GeneralDataState with EquatableMixin { gear = Sequence.fill( hardwareCount.motors, MotorGear.unknown, - ), - wheelSteering = WheelSteering.free; + ); factory GeneralDataState.fromMap(Map map) { return GeneralDataState( @@ -92,7 +88,6 @@ final class GeneralDataState with EquatableMixin { gear: Sequence.fromIterable( map.tryParseAndMapList('gear', MotorGear.fromId), ), - wheelSteering: map.parseAndMap('wheelSteering', WheelSteering.fromId), ); } @@ -102,7 +97,6 @@ final class GeneralDataState with EquatableMixin { final Sequence speed; final Sequence gear; final Sequence batteryPercent; - final WheelSteering wheelSteering; // TODO(Radomir): hardcoded temporarily because there is only one battery // at the moment, and the merged value(mean) is irrelevant @@ -126,8 +120,13 @@ final class GeneralDataState with EquatableMixin { } @override - List get props => - [power, batteryPercent, odometer, speed, gear, wheelSteering]; + List get props => [ + power, + batteryPercent, + odometer, + speed, + gear, + ]; GeneralDataState copyWith({ Sequence? power, @@ -135,7 +134,6 @@ final class GeneralDataState with EquatableMixin { IntWithStatus? odometer, Sequence? speed, Sequence? gear, - WheelSteering? wheelSteering, }) { return GeneralDataState( hardwareCount: hardwareCount, @@ -144,7 +142,6 @@ final class GeneralDataState with EquatableMixin { odometer: odometer ?? this.odometer, speed: speed ?? this.speed, gear: gear ?? this.gear, - wheelSteering: wheelSteering ?? this.wheelSteering, ); } @@ -156,7 +153,6 @@ final class GeneralDataState with EquatableMixin { 'odometer': odometer.toMap(), 'speed': [for (final e in speed) e.toMap()], 'gear': [for (final e in gear) e.id], - 'wheelSteering': wheelSteering.id, }; } } @@ -220,14 +216,6 @@ class GeneralDataCubit extends Cubit with ConsumerBlocMixin { odometer: IntWithStatus.fromBytesConvertible(model, km), ), ); - }) - ..voidOnModel((model) { - emit( - state.copyWith( - wheelSteering: WheelSteering.fromId(model.value) , - ), - ); }); }); } @@ -246,7 +234,6 @@ class GeneralDataCubit extends Cubit with ConsumerBlocMixin { const DataSourceParameterId.batteryPercent2(), const DataSourceParameterId.batteryPower1(), const DataSourceParameterId.batteryPower2(), - const DataSourceParameterId.wheelSteering(), }; @protected diff --git a/lib/domain/data_source/blocs/steering_rack_control_bloc.dart b/lib/domain/data_source/blocs/steering_rack_control_bloc.dart new file mode 100644 index 0000000..5a457a8 --- /dev/null +++ b/lib/domain/data_source/blocs/steering_rack_control_bloc.dart @@ -0,0 +1,127 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; +import 'package:pixel_app_flutter/domain/data_source/extensions/stream.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package/incoming/incoming_data_source_packages.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package/outgoing/outgoing_data_source_packages.dart'; +import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; +import 'package:re_seedwork/re_seedwork.dart'; + +part 'steering_rack_control_bloc.freezed.dart'; + +@freezed +class SteeringRackControlEvent with _$SteeringRackControlEvent { + const factory SteeringRackControlEvent.get() = _Get; + const factory SteeringRackControlEvent.set(SteeringRack steeringRack) = _Set; +} + +typedef SteeringRackControlState + = AsyncData; + +class SteeringRackControlBloc + extends Bloc { + SteeringRackControlBloc({ + required this.dataSource, + this.responseTimeout = const Duration(seconds: 2), + }) : super(const SteeringRackControlState.initial(SteeringRack.free)) { + on<_Get>(_onGet); + on<_Set>(_onSet); + } + + Future _onGet( + _Get event, + Emitter emit, + ) async { + emit(state.inLoading()); + + try { + await dataSource.packageStream + .waitForType( + action: () async { + final result = await dataSource.sendPackage( + OutgoingValueRequestPackage( + parameterId: const DataSourceParameterId.steeringRack(), + ), + ); + + if (result.isError) { + emit(state.inFailure(event)); + } + return result.isError; + }, + onDone: (package) async { + emit( + package.dataModel.when( + success: (value) { + return AsyncData.success(SteeringRack.fromId(value)); + }, + error: () => state.inFailure(event), + ), + ); + }, + timeout: responseTimeout, + ); + } catch (e) { + emit(state.inFailure(event)); + + rethrow; + } + } + + Future _onSet( + _Set event, + Emitter emit, + ) async { + final beforePayload = state.payload; + emit(AsyncData.loading(event.steeringRack)); + + try { + await dataSource.packageStream + .waitForType( + action: () async { + final result = await dataSource.sendPackage( + OutgoingSetValuePackage( + parameterId: const DataSourceParameterId.steeringRack(), + setValueBody: SetUint8Body(value: event.steeringRack.id), + ), + ); + + if (result.isError) { + emit(AsyncData.failure(beforePayload, event)); + } + return result.isError; + }, + onDone: (package) async { + emit( + package.dataModel.when( + success: (value) { + if (value == state.payload.id) { + return state.inSuccess(); + } + return AsyncData.failure( + beforePayload, + event, + ); + }, + error: () => AsyncData.failure( + beforePayload, + event, + ), + ), + ); + }, + timeout: responseTimeout, + ); + } catch (e) { + emit(AsyncData.failure(beforePayload, event)); + + rethrow; + } + } + + @protected + final DataSource dataSource; + + @protected + final Duration responseTimeout; +} diff --git a/lib/domain/data_source/blocs/suspension_control_bloc.dart b/lib/domain/data_source/blocs/suspension_control_bloc.dart index 5d7adbc..10a64d9 100644 --- a/lib/domain/data_source/blocs/suspension_control_bloc.dart +++ b/lib/domain/data_source/blocs/suspension_control_bloc.dart @@ -1,6 +1,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; +import 'package:pixel_app_flutter/domain/data_source/extensions/stream.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package/incoming/incoming_data_source_packages.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package/outgoing/outgoing_data_source_packages.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; @@ -50,7 +51,7 @@ class SuspensionControlBloc try { await dataSource.packageStream - .waitFor( + .waitForType( action: () async { final result = await dataSource.sendPackage( OutgoingValueRequestPackage( @@ -94,7 +95,7 @@ class SuspensionControlBloc try { await dataSource.packageStream - .waitFor( + .waitForType( action: () async { final result = await dataSource.sendPackage( OutgoingValueRequestPackage( @@ -135,7 +136,7 @@ class SuspensionControlBloc try { await dataSource.packageStream - .waitFor( + .waitForType( action: () async { final result = await dataSource.sendPackage( OutgoingSetValuePackage( @@ -190,7 +191,7 @@ class SuspensionControlBloc try { await dataSource.packageStream - .waitFor( + .waitForType( action: () async { final result = await dataSource.sendPackage( OutgoingSetValuePackage( @@ -242,19 +243,3 @@ class SuspensionControlBloc } } } - -extension on Stream { - Future waitFor({ - required Future Function() action, - required Future Function(T value) onDone, - required Duration timeout, - }) async { - final future = firstWhere((package) => package is T).timeout(timeout); - - final stop = await action(); - - if (stop) return; - - await onDone((await future) as T); - } -} diff --git a/lib/domain/data_source/data_source.dart b/lib/domain/data_source/data_source.dart index f9f0b5c..22ef9fa 100644 --- a/lib/domain/data_source/data_source.dart +++ b/lib/domain/data_source/data_source.dart @@ -1,7 +1,6 @@ // blocs export 'blocs/battery_data_cubit.dart'; export 'blocs/change_gear_bloc.dart'; -export 'blocs/change_wheel_steering_bloc.dart'; export 'blocs/data_source_authorization_cubit.dart'; export 'blocs/data_source_connect_bloc.dart'; export 'blocs/data_source_connection_status_cubit.dart'; @@ -15,6 +14,7 @@ export 'blocs/lights_cubit.dart'; export 'blocs/motor_data_cubit.dart'; export 'blocs/outgoing_packages_cubit.dart'; export 'blocs/select_data_source_bloc.dart'; +export 'blocs/steering_rack_control_bloc.dart'; export 'blocs/suspension_control_bloc.dart'; export 'blocs/toggle_state_error.dart'; @@ -28,9 +28,12 @@ export 'models/data_source_with_address.dart'; export 'models/developer_tools_parameters.dart'; export 'models/hardware_count.dart'; export 'models/int_with_status.dart'; +export 'models/motor_gear.dart'; +export 'models/motor_roll_direction.dart'; export 'models/package/data_source_package.dart'; export 'models/package_data/bytes_converter.dart'; export 'models/serial_number.dart'; +export 'models/steering_rack.dart'; export 'models/suspension_mode.dart'; export 'models/usb_port_parameters.dart'; diff --git a/lib/domain/data_source/extensions/stream.dart b/lib/domain/data_source/extensions/stream.dart new file mode 100644 index 0000000..111429b --- /dev/null +++ b/lib/domain/data_source/extensions/stream.dart @@ -0,0 +1,15 @@ +extension WaitForStreamExtension on Stream { + Future waitForType({ + required Future Function() action, + required Future Function(T value) onDone, + required Duration timeout, + }) async { + final future = firstWhere((package) => package is T).timeout(timeout); + + final stop = await action(); + + if (stop) return; + + await onDone((await future) as T); + } +} diff --git a/lib/domain/data_source/models/data_source_parameter_id.dart b/lib/domain/data_source/models/data_source_parameter_id.dart index 4e6c335..79c8974 100644 --- a/lib/domain/data_source/models/data_source_parameter_id.dart +++ b/lib/domain/data_source/models/data_source_parameter_id.dart @@ -136,8 +136,7 @@ abstract class DataSourceParameterId { const factory DataSourceParameterId.transmission4() = Transmission4ParameterId; - const factory DataSourceParameterId.wheelSteering() = - WheelSteeringParameterId; + const factory DataSourceParameterId.steeringRack() = SteeringRackParameterId; const factory DataSourceParameterId.motorTemperature1() = MotorTemperature1ParameterId; @@ -272,7 +271,7 @@ abstract class DataSourceParameterId { bool get isTransmission3 => this is Transmission3ParameterId; bool get isTransmission4 => this is Transmission4ParameterId; - bool get isWheelSteering => this is WheelSteeringParameterId; + bool get isSteeringRack => this is SteeringRackParameterId; bool get isMotorTemperature1 => this is MotorTemperature1ParameterId; bool get isMotorTemperature2 => this is MotorTemperature2ParameterId; @@ -377,7 +376,7 @@ abstract class DataSourceParameterId { DataSourceParameterId.transmission2(), DataSourceParameterId.transmission3(), DataSourceParameterId.transmission4(), - DataSourceParameterId.wheelSteering(), + DataSourceParameterId.steeringRack(), DataSourceParameterId.motorTemperature1(), DataSourceParameterId.motorTemperature2(), DataSourceParameterId.motorTemperature3(), @@ -677,8 +676,8 @@ class Transmission4ParameterId extends DataSourceParameterId { const Transmission4ParameterId() : super(0x0137); } -class WheelSteeringParameterId extends DataSourceParameterId { - const WheelSteeringParameterId() : super(0x01A4); +class SteeringRackParameterId extends DataSourceParameterId { + const SteeringRackParameterId() : super(0x01A4); } class MotorTemperature1ParameterId extends DataSourceParameterId { diff --git a/lib/domain/data_source/models/motor_gear.dart b/lib/domain/data_source/models/motor_gear.dart new file mode 100644 index 0000000..c12f6ea --- /dev/null +++ b/lib/domain/data_source/models/motor_gear.dart @@ -0,0 +1,40 @@ +import 'dart:math' show Random; + +enum MotorGear { + reverse(0x02), + neutral(0x00), + drive(0x01), + low(0x04), + boost(0x08), + unknown(0xFF); + + const MotorGear(this.id); + final int id; + + static MotorGear fromId(int id) { + return MotorGear.values.firstWhere( + (element) => element.id == id, + orElse: () => MotorGear.unknown, + ); + } + + R when({ + required R Function() reverse, + required R Function() neutral, + required R Function() drive, + required R Function() low, + required R Function() boost, + required R Function() unknown, + }) { + return switch (this) { + MotorGear.reverse => reverse(), + MotorGear.neutral => neutral(), + MotorGear.drive => drive(), + MotorGear.low => low(), + MotorGear.boost => boost(), + MotorGear.unknown => unknown(), + }; + } + + static MotorGear get random => values[Random().nextInt(values.length)]; +} diff --git a/lib/domain/data_source/models/motor_roll_direction.dart b/lib/domain/data_source/models/motor_roll_direction.dart new file mode 100644 index 0000000..f7d7685 --- /dev/null +++ b/lib/domain/data_source/models/motor_roll_direction.dart @@ -0,0 +1,35 @@ +import 'dart:math' show Random; + +enum MotorRollDirection { + stop(0x00), + forward(0x01), + reverse(0x02), + unknown(0xFF); + + const MotorRollDirection(this.id); + final int id; + + static MotorRollDirection fromId(int id) { + return MotorRollDirection.values.firstWhere( + (element) => element.id == id, + orElse: () => MotorRollDirection.unknown, + ); + } + + R when({ + required R Function() stop, + required R Function() forward, + required R Function() reverse, + required R Function() unknown, + }) { + return switch (this) { + MotorRollDirection.stop => stop(), + MotorRollDirection.forward => forward(), + MotorRollDirection.reverse => reverse(), + MotorRollDirection.unknown => unknown(), + }; + } + + static MotorRollDirection get random => + values[Random().nextInt(values.length)]; +} diff --git a/lib/domain/data_source/models/package/data_source_incoming_package.dart b/lib/domain/data_source/models/package/data_source_incoming_package.dart index 94ab38a..66fa009 100644 --- a/lib/domain/data_source/models/package/data_source_incoming_package.dart +++ b/lib/domain/data_source/models/package/data_source_incoming_package.dart @@ -125,6 +125,8 @@ abstract class DataSourceIncomingPackage SuspensionModeIncomingDataSourcePackage.new, SuspensionManualValueIncomingDataSourcePackage.new, // + SteeringRackIncomingDataSourcePackage.new, + // ErrorWithCodeAndSectionIncomingDataSourcePackage.new, // CustomIncomingDataSourcePackage.new, diff --git a/lib/domain/data_source/models/package/incoming/incoming_data_source_packages.dart b/lib/domain/data_source/models/package/incoming/incoming_data_source_packages.dart index 0d2e300..358b671 100644 --- a/lib/domain/data_source/models/package/incoming/incoming_data_source_packages.dart +++ b/lib/domain/data_source/models/package/incoming/incoming_data_source_packages.dart @@ -29,6 +29,7 @@ export 'reverse_light.dart'; export 'rpm.dart'; export 'side_beam.dart'; export 'speed.dart'; +export 'steering_rack.dart'; export 'suspension_manual_value.dart'; export 'suspension_mode.dart'; export 'turn_signal.dart'; diff --git a/lib/domain/data_source/models/package/incoming/steering_rack.dart b/lib/domain/data_source/models/package/incoming/steering_rack.dart new file mode 100644 index 0000000..4016580 --- /dev/null +++ b/lib/domain/data_source/models/package/incoming/steering_rack.dart @@ -0,0 +1,9 @@ +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; + +class SteeringRackIncomingDataSourcePackage + extends SetUint8ResultIncomingDataSourcePackage { + SteeringRackIncomingDataSourcePackage(super.source); + + @override + bool get validParameterId => parameterId.isSteeringRack; +} diff --git a/lib/domain/data_source/models/package/incoming/wheelSteering.dart b/lib/domain/data_source/models/package/incoming/wheelSteering.dart deleted file mode 100644 index 659f861..0000000 --- a/lib/domain/data_source/models/package/incoming/wheelSteering.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:pixel_app_flutter/domain/data_source/models/package/data_source_incoming_package.dart'; - -class WheelSteeringIncomingDataSourcePackage - extends Uint8WithStatusIncomingDataSourcePackage { - WheelSteeringIncomingDataSourcePackage(super.source); - - @override - bool get validParameterId => parameterId.isWheelSteering; -} diff --git a/lib/domain/data_source/models/package_data/function_id.dart b/lib/domain/data_source/models/package_data/function_id.dart index 9b40011..b53e0dc 100644 --- a/lib/domain/data_source/models/package_data/function_id.dart +++ b/lib/domain/data_source/models/package_data/function_id.dart @@ -56,6 +56,9 @@ enum FunctionId { // static const errorEventId = 0xE6; static const okEventId = 0x65; + + bool get isRequestValue => this == FunctionId.requestValue; + bool get isSetValueWithParam => this == FunctionId.setValueWithParam; } enum AuthorizationFunctionId { diff --git a/lib/domain/data_source/models/package_data/implementations/motor_gear_and_roll.dart b/lib/domain/data_source/models/package_data/implementations/motor_gear_and_roll.dart index fd7f40a..1ef3bca 100644 --- a/lib/domain/data_source/models/package_data/implementations/motor_gear_and_roll.dart +++ b/lib/domain/data_source/models/package_data/implementations/motor_gear_and_roll.dart @@ -1,82 +1,8 @@ -import 'dart:math'; - +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; import 'package:pixel_app_flutter/domain/data_source/extensions/int.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package_data/wrappers/bytes_convertible_with_status.dart'; -enum MotorGear { - reverse(0x02), - neutral(0x00), - drive(0x01), - low(0x04), - boost(0x08), - unknown(0xFF); - - const MotorGear(this.id); - final int id; - - static MotorGear fromId(int id) { - return MotorGear.values.firstWhere( - (element) => element.id == id, - orElse: () => MotorGear.unknown, - ); - } - - R when({ - required R Function() reverse, - required R Function() neutral, - required R Function() drive, - required R Function() low, - required R Function() boost, - required R Function() unknown, - }) { - return switch (this) { - MotorGear.reverse => reverse(), - MotorGear.neutral => neutral(), - MotorGear.drive => drive(), - MotorGear.low => low(), - MotorGear.boost => boost(), - MotorGear.unknown => unknown(), - }; - } - - static MotorGear get random => values[Random().nextInt(values.length)]; -} - -enum MotorRollDirection { - stop(0x00), - forward(0x01), - reverse(0x02), - unknown(0xFF); - - const MotorRollDirection(this.id); - final int id; - - static MotorRollDirection fromId(int id) { - return MotorRollDirection.values.firstWhere( - (element) => element.id == id, - orElse: () => MotorRollDirection.unknown, - ); - } - - R when({ - required R Function() stop, - required R Function() forward, - required R Function() reverse, - required R Function() unknown, - }) { - return switch (this) { - MotorRollDirection.stop => stop(), - MotorRollDirection.forward => forward(), - MotorRollDirection.reverse => reverse(), - MotorRollDirection.unknown => unknown(), - }; - } - - static MotorRollDirection get random => - values[Random().nextInt(values.length)]; -} - class MotorGearAndRoll extends IntBytesConvertibleWithStatus { MotorGearAndRoll({ required this.gear, diff --git a/lib/domain/data_source/models/steering_rack.dart b/lib/domain/data_source/models/steering_rack.dart new file mode 100644 index 0000000..2cad143 --- /dev/null +++ b/lib/domain/data_source/models/steering_rack.dart @@ -0,0 +1,37 @@ +import 'dart:math' show Random; + +enum SteeringRack { + free(0), + alignment(1), + tankTurn(2), + crabWalk(3), + blocked(4); + + const SteeringRack(this.id); + + final int id; + + static SteeringRack fromId(int id) { + return SteeringRack.values.firstWhere( + (element) => element.id == id, + ); + } + + T when({ + required T Function() free, + required T Function() alignment, + required T Function() tankTurn, + required T Function() crabWalk, + required T Function() blocked, + }) { + return switch (this) { + SteeringRack.free => free(), + SteeringRack.alignment => alignment(), + SteeringRack.tankTurn => tankTurn(), + SteeringRack.crabWalk => crabWalk(), + SteeringRack.blocked => blocked(), + }; + } + + static SteeringRack get random => values[Random().nextInt(values.length)]; +} diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index afbb156..d85b789 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -317,6 +317,14 @@ } } }, + "steeringRackModeDialogTitle": "Steering rack mode", + "errorGettingSteeringRackModeMessage": "Error getting steering rack mode", + "errorSettingSteeringRackModeMessage": "Error setting steering rack mode", + "freeSteeringRack": "Free", + "alignmentSteeringRack": "Alignment", + "crabWalkSteeringRack": "Crab walk", + "tankTurnSteeringRack": "Tank turn", + "blockedSteeringRack": "Blocked", "ledPanelSwitcherDialogTitle": "Run LED panel configurations", "noLEDConfigurationsMessage": "There are no configurations yet", "shutdownConfigByTimerMessage": "By timer: {shutdownTimeMillis} ms", diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 30131b3..3cb8b88 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -317,6 +317,14 @@ } } }, + "steeringRackModeDialogTitle": "Режим рулевой рейки", + "errorGettingSteeringRackModeMessage": "Ошибка при получении режима рулевой рейки", + "errorSettingSteeringRackModeMessage": "Ошибка при установке режима рулевой рейки", + "freeSteeringRack": "Свободный", + "alignmentSteeringRack": "Выравнивание", + "crabWalkSteeringRack": "Крабовый ход", + "tankTurnSteeringRack": "Танковый поворот", + "blockedSteeringRack": "Заблокированный", "ledPanelSwitcherDialogTitle": "Запуск конфигураций светодиодной панели", "noLEDConfigurationsMessage": "Пока конфигураций нет", "shutdownConfigByTimerMessage": "По таймеру: {shutdownTimeMillis} мс", diff --git a/lib/presentation/app/extensions.dart b/lib/presentation/app/extensions.dart index 3484df1..a2fe402 100644 --- a/lib/presentation/app/extensions.dart +++ b/lib/presentation/app/extensions.dart @@ -1,5 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; import 'package:pixel_app_flutter/l10n/l10n.dart'; import 'package:pixel_app_flutter/presentation/app/colors.dart'; diff --git a/lib/presentation/routes/main_router.dart b/lib/presentation/routes/main_router.dart index 357b851..7fb1d2b 100644 --- a/lib/presentation/routes/main_router.dart +++ b/lib/presentation/routes/main_router.dart @@ -11,7 +11,6 @@ import 'package:pixel_app_flutter/app/scopes/flows/selected_data_source_scope.da import 'package:pixel_app_flutter/app/scopes/screens/charging_screen_wrapper.dart'; import 'package:pixel_app_flutter/app/scopes/screens/motor_screen_wrapper.dart'; import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; import 'package:pixel_app_flutter/domain/led_panel/led_panel.dart'; import 'package:pixel_app_flutter/domain/user_defined_buttons/user_defined_buttons.dart'; import 'package:pixel_app_flutter/presentation/app/colors.dart'; @@ -31,8 +30,8 @@ import 'package:pixel_app_flutter/presentation/screens/developer_tools/widgets/i import 'package:pixel_app_flutter/presentation/screens/developer_tools/widgets/slider_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/general/general_screen.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/change_gear_dialog.dart'; -import 'package:pixel_app_flutter/presentation/screens/general/widgets/change_wheel_steering_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/led_panel_switcher_dialog.dart'; +import 'package:pixel_app_flutter/presentation/screens/general/widgets/steering_rack_control_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/suspension_control_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/home/home_screen.dart'; import 'package:pixel_app_flutter/presentation/screens/navigator/navigator_screen.dart'; @@ -89,11 +88,6 @@ class MainRouter extends RootStackRouter { page: ChangeGearDialogRoute.page, customRouteBuilder: dialogRouteBuilder, ), - CustomRoute( - path: 'change-wheel-steering-dialog', - page: ChangeWheelSteeringDialogRoute.page, - customRouteBuilder: dialogRouteBuilder, - ), ], ), // diff --git a/lib/presentation/routes/subroutes/home_route.dart b/lib/presentation/routes/subroutes/home_route.dart index 1f7b540..dc3f1ec 100644 --- a/lib/presentation/routes/subroutes/home_route.dart +++ b/lib/presentation/routes/subroutes/home_route.dart @@ -22,6 +22,11 @@ final _homeRoute = AutoRoute( page: SuspensionControlDialogRoute.page, customRouteBuilder: noBarrierDialogRouteBuilder, ), + CustomRoute( + path: 'steering-rack-control-dialog', + page: SteeringRackControlDialogRoute.page, + customRouteBuilder: noBarrierDialogRouteBuilder, + ), ], ), AutoRoute( diff --git a/lib/presentation/screens/general/general_screen.dart b/lib/presentation/screens/general/general_screen.dart index c0eee86..8965cce 100644 --- a/lib/presentation/screens/general/general_screen.dart +++ b/lib/presentation/screens/general/general_screen.dart @@ -8,9 +8,9 @@ import 'package:pixel_app_flutter/presentation/screens/general/widgets/general_i import 'package:pixel_app_flutter/presentation/screens/general/widgets/led_switcher_button.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/light_state_error_listener.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/overlay_data_sender.dart'; +import 'package:pixel_app_flutter/presentation/screens/general/widgets/steering_rack_control_button.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/suspension_control_button.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/user_defined_buttons_end_drawer.dart'; -import 'package:pixel_app_flutter/presentation/screens/general/widgets/wheel_steering_widget.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/wipers_switcher_button.dart'; import 'package:pixel_app_flutter/presentation/widgets/app/organisms/screen_data.dart'; import 'package:pixel_app_flutter/presentation/widgets/common/atoms/responsive_padding.dart'; @@ -18,6 +18,7 @@ import 'package:pixel_app_flutter/presentation/widgets/common/molecules/speed_wi import 'package:pixel_app_flutter/presentation/widgets/common/molecules/statistic_widget.dart'; import 'package:pixel_app_flutter/presentation/widgets/tablet/molecules/blinker_button.dart'; import 'package:pixel_app_flutter/presentation/widgets/tablet/organisms/tablet_upper_info_panel.dart'; +import 'package:re_widgets/re_widgets.dart'; @RoutePage() class GeneralScreen extends StatelessWidget { @@ -93,12 +94,7 @@ class TabletGeneralScreenBody extends StatelessWidget { screenFlexRange: (600, 700), valueClampRange: (420, 460), ), - child: Row( - children: [ - GearWidget(screenSize: size), - WheelSteeringWidget(screenSize: size), - ], - ), + child: GearWidget(screenSize: size), ), const Positioned( right: 0, @@ -131,12 +127,7 @@ class HandsetGeneralScreenBody extends StatelessWidget { if (landscape) const StatisticWidget() else - Column( - children: [ - GearWidget(screenSize: size), - WheelSteeringWidget(screenSize: size), - ], - ), + GearWidget(screenSize: size), ], ), ), @@ -144,29 +135,31 @@ class HandsetGeneralScreenBody extends StatelessWidget { const SizedBox(height: 32), const StatisticWidget(useWrap: true), const SizedBox(height: 32), - const LEDSwitcherButton(), - const SizedBox(height: 16), - const WipersSwitcherButton(), - const SizedBox(height: 16), - const SuspensionControlButton(), - ] else ...[ - Row( - children: [ - GearWidget(screenSize: size), - const SizedBox(width: 16), - WheelSteeringWidget(screenSize: size), - ], - ), - const SizedBox(height: 16), - const Row( + const Wrap( + spacing: 16, + runSpacing: 16, children: [ - LEDSwitcherButton(), - SizedBox(width: 16), WipersSwitcherButton(), - SizedBox(width: 16), SuspensionControlButton(), + SteeringRackControlButton(), + LEDSwitcherButton(), ], ), + ] else ...[ + GearWidget(screenSize: size), + const SizedBox(height: 16), + FadeSingleChildScrollView( + axis: Axis.horizontal, + child: const Row( + spacing: 16, + children: [ + WipersSwitcherButton(), + SuspensionControlButton(), + SteeringRackControlButton(), + LEDSwitcherButton(), + ], + ), + ), ], ], ); diff --git a/lib/presentation/screens/general/widgets/car_widget.dart b/lib/presentation/screens/general/widgets/car_widget.dart index 685ee10..d4e4445 100644 --- a/lib/presentation/screens/general/widgets/car_widget.dart +++ b/lib/presentation/screens/general/widgets/car_widget.dart @@ -7,6 +7,7 @@ import 'package:pixel_app_flutter/l10n/l10n.dart'; import 'package:pixel_app_flutter/presentation/app/extensions.dart'; import 'package:pixel_app_flutter/presentation/app/icons.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/led_switcher_button.dart'; +import 'package:pixel_app_flutter/presentation/screens/general/widgets/steering_rack_control_button.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/suspension_control_button.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/wipers_switcher_button.dart'; import 'package:pixel_app_flutter/presentation/widgets/common/atoms/icon_button.dart'; @@ -202,6 +203,12 @@ class _CarWidgetState extends State { left: -140, child: WipersSwitcherButton(), ), + // + const Positioned( + top: 50, + left: -140, + child: SteeringRackControlButton(), + ), ], ], ), diff --git a/lib/presentation/screens/general/widgets/change_gear_dialog.dart b/lib/presentation/screens/general/widgets/change_gear_dialog.dart index 6375ccc..cddea12 100644 --- a/lib/presentation/screens/general/widgets/change_gear_dialog.dart +++ b/lib/presentation/screens/general/widgets/change_gear_dialog.dart @@ -1,6 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; import 'package:pixel_app_flutter/presentation/app/colors.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/gear_symbol_widget.dart'; import 'package:pixel_app_flutter/presentation/widgets/app/organisms/screen_data.dart'; diff --git a/lib/presentation/screens/general/widgets/change_wheel_steering_dialog.dart b/lib/presentation/screens/general/widgets/change_wheel_steering_dialog.dart deleted file mode 100644 index ca31088..0000000 --- a/lib/presentation/screens/general/widgets/change_wheel_steering_dialog.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; -import 'package:pixel_app_flutter/presentation/app/colors.dart'; -import 'package:pixel_app_flutter/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart'; -import 'package:pixel_app_flutter/presentation/widgets/app/organisms/screen_data.dart'; -import 'package:re_seedwork/re_seedwork.dart'; - -@RoutePage(name: 'ChangeWheelSteeringDialogRoute') -class ChangeWheelSteeringDialog extends StatefulWidget { - const ChangeWheelSteeringDialog({super.key, required this.padding}); - - final (double? left, double? top, double? right, double? bottom) padding; - - @override - State createState() => - _ChangeWheelSteeringDialogState(); -} - -class _ChangeWheelSteeringDialogState extends State { - @protected - static const kBorderRadius = BorderRadius.all(Radius.circular(10)); - - @protected - static const kDialogActiveTimeSecs = 2; - - bool isInit = true; - - @override - void initState() { - super.initState(); - Future.delayed(const Duration(seconds: kDialogActiveTimeSecs)).then( - (value) { - if (!mounted) return; - context.router.maybePop(); - }, - ); - } - - @override - void didChangeDependencies() { - if (!isInit && mounted) { - context.router.maybePop(); - } - isInit = false; - super.didChangeDependencies(); - } - - @override - Widget build(BuildContext context) { - final size = Screen.of(context).size; - - return Stack( - children: [ - Positioned( - left: widget.padding.$1, - top: widget.padding.$2, - right: widget.padding.$3, - bottom: widget.padding.$4, - child: Material( - borderRadius: kBorderRadius, - child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: kBorderRadius, - color: context.colors.primaryAccent.withAlpha(50), - border: Border.all( - color: context.colors.primary, - width: 2, - ), - ), - child: IntrinsicWidth( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (final value in WheelSteering.values) - InkWell( - onTap: () { - context.router.maybePop(value); - }, - borderRadius: kBorderRadius, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: WheelSteeringSymbolWidget( - value: value.index, - screenSize: size, - values: [ - 'free', - 'align', - 'turn', - 'diagonal', - 'blocked', - ], // TODO(Alexandr): replace with icons - ), - ), - ), - ] - .divideBy( - Divider( - thickness: 1, - indent: 6, - endIndent: 6, - color: context.colors.text, - ), - ) - .toList(), - ), - ), - ), - ), - ), - ], - ); - } -} diff --git a/lib/presentation/screens/general/widgets/gear_symbol_widget.dart b/lib/presentation/screens/general/widgets/gear_symbol_widget.dart index 8cbfe38..20d88b8 100644 --- a/lib/presentation/screens/general/widgets/gear_symbol_widget.dart +++ b/lib/presentation/screens/general/widgets/gear_symbol_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; import 'package:pixel_app_flutter/presentation/app/colors.dart'; import 'package:pixel_app_flutter/presentation/app/extensions.dart'; diff --git a/lib/presentation/screens/general/widgets/gear_widget.dart b/lib/presentation/screens/general/widgets/gear_widget.dart index fe08f80..28f25c0 100644 --- a/lib/presentation/screens/general/widgets/gear_widget.dart +++ b/lib/presentation/screens/general/widgets/gear_widget.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pixel_app_flutter/data/services/data_source/demo_data_source.dart'; import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; import 'package:pixel_app_flutter/l10n/l10n.dart'; import 'package:pixel_app_flutter/presentation/routes/main_router.dart'; import 'package:pixel_app_flutter/presentation/screens/general/widgets/gear_symbol_widget.dart'; diff --git a/lib/presentation/screens/general/widgets/steering_rack_control_button.dart b/lib/presentation/screens/general/widgets/steering_rack_control_button.dart new file mode 100644 index 0000000..f308bd7 --- /dev/null +++ b/lib/presentation/screens/general/widgets/steering_rack_control_button.dart @@ -0,0 +1,44 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; +import 'package:pixel_app_flutter/l10n/l10n.dart'; +import 'package:pixel_app_flutter/presentation/routes/main_router.dart'; + +class SteeringRackControlButton extends StatelessWidget { + const SteeringRackControlButton({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return SizedBox( + width: 120, + child: ActionChip( + avatar: const Icon( + Icons.swap_horiz_rounded, + size: 17, + ), + labelStyle: + const TextStyle(fontFeatures: [FontFeature.tabularFigures()]), + label: Center( + child: Text( + state.payload.when( + alignment: () => context.l10n.alignmentSteeringRack, + blocked: () => context.l10n.blockedSteeringRack, + crabWalk: () => context.l10n.crabWalkSteeringRack, + free: () => context.l10n.freeSteeringRack, + tankTurn: () => context.l10n.tankTurnSteeringRack, + ), + textAlign: TextAlign.center, + ), + ), + onPressed: () { + context.router.push(const SteeringRackControlDialogRoute()); + }, + ), + ); + }, + ); + } +} diff --git a/lib/presentation/screens/general/widgets/steering_rack_control_dialog.dart b/lib/presentation/screens/general/widgets/steering_rack_control_dialog.dart new file mode 100644 index 0000000..0cd7bbe --- /dev/null +++ b/lib/presentation/screens/general/widgets/steering_rack_control_dialog.dart @@ -0,0 +1,120 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; +import 'package:pixel_app_flutter/l10n/l10n.dart'; +import 'package:pixel_app_flutter/presentation/app/colors.dart'; + +@RoutePage(name: 'SteeringRackControlDialogRoute') +class SteeringRackControlDialog extends StatefulWidget { + const SteeringRackControlDialog({super.key}); + + @override + State createState() => + _SuspensionControlDialogState(); +} + +class _SuspensionControlDialogState extends State { + late final StreamSubscription streamSubscription; + + @override + void initState() { + super.initState(); + streamSubscription = context.read().stream.listen( + (state) { + if (state.isSuccess && mounted) { + context.router.maybePop(); + } + }, + ); + } + + @override + void dispose() { + streamSubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(context.l10n.steeringRackModeDialogTitle), + content: BlocBuilder( + builder: (context, state) { + return AnimatedSize( + alignment: Alignment.topCenter, + duration: const Duration(milliseconds: 300), + child: IgnorePointer( + ignoring: state.isLoading, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final mode in SteeringRack.values) + RadioListTile( + value: mode.id, + groupValue: state.payload.id, + onChanged: (newMode) { + if (newMode == null) return; + context + .read() + .add(SteeringRackControlEvent.set(mode)); + }, + title: Text( + mode.when( + alignment: () => + context.l10n.alignmentSteeringRack, + blocked: () => context.l10n.blockedSteeringRack, + crabWalk: () => context.l10n.crabWalkSteeringRack, + free: () => context.l10n.freeSteeringRack, + tankTurn: () => context.l10n.tankTurnSteeringRack, + ), + ), + ), + state.maybeWhen( + orElse: (payload) => const SizedBox.shrink(), + failure: (payload, error) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + error?.when( + set: (_) => context.l10n + .errorSettingSteeringRackModeMessage, + get: () => context.l10n + .errorGettingSteeringRackModeMessage, + ) ?? + context + .l10n.errorSettingSteeringRackModeMessage, + textAlign: TextAlign.center, + style: TextStyle( + color: context.colors.errorPastel, + ), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + if (error == null) return; + context + .read() + .add(error); + }, + child: Text(context.l10n.retryButtonCaption), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart b/lib/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart deleted file mode 100644 index 01bd406..0000000 --- a/lib/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:pixel_app_flutter/presentation/app/colors.dart'; - -class WheelSteeringSymbolWidget extends StatelessWidget { - const WheelSteeringSymbolWidget({ - required this.value, - required this.values, - required this.screenSize, - this.inactive = false, - super.key, - }); - - @protected - final int value; - - @protected - final Size screenSize; - - @protected - final bool inactive; - - @protected - final List values; - - @protected - static const kTextStyle = TextStyle( - height: 1.2, - fontSize: 32, - fontStyle: FontStyle.normal, - fontWeight: FontWeight.w700, - fontFeatures: [FontFeature.tabularFigures()], - ); - - @override - Widget build(BuildContext context) { - return Text( - values.elementAt(value) ?? '', - style: kTextStyle.copyWith( - color: inactive ? context.colors.disabled : context.colors.text, - ), - ); - } -} diff --git a/lib/presentation/screens/general/widgets/wheel_steering_widget.dart b/lib/presentation/screens/general/widgets/wheel_steering_widget.dart deleted file mode 100644 index d59fb14..0000000 --- a/lib/presentation/screens/general/widgets/wheel_steering_widget.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:pixel_app_flutter/domain/data_source/data_source.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; -import 'package:pixel_app_flutter/l10n/l10n.dart'; -import 'package:pixel_app_flutter/presentation/routes/main_router.dart'; -import 'package:pixel_app_flutter/presentation/screens/general/widgets/wheel_steering_symbol_widget.dart'; -import 'package:pixel_app_flutter/presentation/widgets/app/organisms/screen_data.dart'; -import 'package:re_widgets/re_widgets.dart'; - -class WheelSteeringWidget extends StatelessWidget { - const WheelSteeringWidget({ - super.key, - required this.screenSize, - }); - - @protected - final Size screenSize; - - @protected - static const kHorizontalPadding = 10; - - @override - Widget build(BuildContext context) { - return BlocSelector( - selector: (state) => state.wheelSteering, - builder: (context, value) { - return BlocConsumer( - listenWhen: (previous, current) => current.isFailure, - listener: (context, state) { - context.showSnackBar(context.l10n.errorChangeWheelSteeringMessage); - }, - builder: (context, settingGearState) { - return InkWell( - borderRadius: const BorderRadius.all(Radius.circular(10)), - onTap: settingGearState.isLoading - ? null - : () async { - final s = Screen.of(context, watch: false); - final rect = context.globalPaintBounds; - final padding = s.orientation == Orientation.portrait && - s.type == ScreenType.handset - ? ( - null, - rect?.top ?? 0, - s.size.width - - (rect?.left ?? 0) + - kHorizontalPadding, - null - ) - : ( - (rect?.left ?? 0) + - (rect?.width ?? 0) + - kHorizontalPadding, - rect?.top ?? 0, - null, - null, - ); - - final wheelSteeringValue = - await context.router.push( - ChangeWheelSteeringDialogRoute( - padding: padding, - ), - ); - - if (wheelSteeringValue == null || !context.mounted) { - return; - } - - context.read().add( - ChangeWheelSteeringEvent.change(wheelSteeringValue), - ); - }, - child: WheelSteeringSymbolWidget( - value: settingGearState.maybeWhen( - orElse: (payload) => value.index, - loading: (payload) => payload.index, - ), - values: const [ - 'free', // TODO(Alexandr): replace with icons - 'align', - 'turn', - 'diagonal', - 'blocked', - ], - inactive: settingGearState.isLoading, - screenSize: screenSize, - ), - ); - }, - ); - }, - ); - } -} - -extension on BuildContext { - Rect? get globalPaintBounds { - final renderObject = findRenderObject(); - final translation = renderObject?.getTransformTo(null).getTranslation(); - if (translation != null && renderObject?.paintBounds != null) { - final offset = Offset(translation.x, translation.y); - return renderObject!.paintBounds.shift(offset); - } else { - return null; - } - } -} From 379a2b9a840d2245ede7db7ff5ec92cadbd5d8bc Mon Sep 17 00:00:00 2001 From: Radomir Epur Date: Mon, 17 Feb 2025 13:01:53 +0300 Subject: [PATCH 3/3] refactor: remove unused model(WheelSteering) and localization string --- .../implementations/wheel_steering.dart | 97 ------------------- .../models/package_data/package_data.dart | 1 - lib/l10n/arb/app_en.arb | 1 - lib/l10n/arb/app_ru.arb | 1 - 4 files changed, 100 deletions(-) delete mode 100644 lib/domain/data_source/models/package_data/implementations/wheel_steering.dart diff --git a/lib/domain/data_source/models/package_data/implementations/wheel_steering.dart b/lib/domain/data_source/models/package_data/implementations/wheel_steering.dart deleted file mode 100644 index dd61a9f..0000000 --- a/lib/domain/data_source/models/package_data/implementations/wheel_steering.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'dart:math'; - -import 'package:pixel_app_flutter/domain/data_source/extensions/int.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart'; -import 'package:pixel_app_flutter/domain/data_source/models/package_data/wrappers/bytes_convertible_with_status.dart'; - -enum WheelSteering { - free(0x00), - align(0x01), - fastTurn(0x02), - diagonal(0x03), - blocked(0x04); - - const WheelSteering(this.id); - final int id; - - static WheelSteering fromId(int id) { - return WheelSteering.values.firstWhere( - (element) => element.id == id, - orElse: () => WheelSteering.free, - ); - } - - R when({ - required R Function() free, - required R Function() align, - required R Function() fastTurn, - required R Function() diagonal, - required R Function() blocked, - }) { - return switch (this) { - WheelSteering.free => free(), - WheelSteering.align => align(), - WheelSteering.fastTurn => fastTurn(), - WheelSteering.diagonal => diagonal(), - WheelSteering.blocked => blocked(), - }; - } - - static WheelSteering get random => values[Random().nextInt(values.length)]; -} - -class WheelSteeringPacket extends IntBytesConvertibleWithStatus { - WheelSteeringPacket({ - required this.wheelSteering, - required super.status, - }) : super( - value: _toUint8( - wheelSteering: wheelSteering, - ), - ); - - factory WheelSteeringPacket.unknown() => WheelSteeringPacket( - wheelSteering: WheelSteering.free, - status: PeriodicValueStatus.normal, - ); - - WheelSteeringPacket.fromId({ - required this.wheelSteering, - required super.id, - }) : super.fromId( - value: _toUint8( - wheelSteering: wheelSteering, - ), - ); - - factory WheelSteeringPacket.builder(int functionId, int value) { - final bytes = value.toBytesUint8; - - return WheelSteeringPacket.fromId( - id: functionId, - wheelSteering: WheelSteering.fromId(bytes[0]), - ); - } - - final WheelSteering wheelSteering; - - static int _toUint8({ - required WheelSteering wheelSteering, - }) { - return [ - wheelSteering.id, - ].toIntFromUint8; - } - - @override - List get props => [ - ...super.props, - wheelSteering, - ]; - - static Uint8WithStatusBytesConverter get converter => - const Uint8WithStatusBytesConverter(WheelSteeringPacket.builder); - - @override - BytesConverter get bytesConverter => converter; -} diff --git a/lib/domain/data_source/models/package_data/package_data.dart b/lib/domain/data_source/models/package_data/package_data.dart index 960c814..ac11532 100644 --- a/lib/domain/data_source/models/package_data/package_data.dart +++ b/lib/domain/data_source/models/package_data/package_data.dart @@ -25,7 +25,6 @@ export 'implementations/two_uint16_with_status_body.dart'; export 'implementations/uint16_with_status_body.dart'; export 'implementations/uint32_with_status_body.dart'; export 'implementations/motor_gear_and_roll.dart'; -export 'implementations/wheel_steering.dart'; export 'implementations/int16_with_status_body.dart'; export 'implementations/set_int8_body.dart'; export 'implementations/set_uint8_body.dart'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d85b789..e2c69f2 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -290,7 +290,6 @@ "boostGearShort": "B", "unknownGearShort": "U", "errorSwitchingGearMessage": "Error switching the gear", - "errorChangeWheelSteeringMessage": "Error switching wheels steering mode", "stopBeforeSwitchingGearMessage": "To switch the gear, you must first stop", "reverseMotorRollDirectionShort": "R", "unknownMotorRollDirectionShort": "U", diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 3cb8b88..8cb39c1 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -290,7 +290,6 @@ "boostGearShort": "B", "unknownGearShort": "U", "errorSwitchingGearMessage": "Ошибка переключения передачи", - "errorChangeWheelSteeringMessage": "Ошибка переключения режима поворота", "stopBeforeSwitchingGearMessage": "Для переключения передачи нужно сначала остановиться", "reverseMotorRollDirectionShort": "R", "unknownMotorRollDirectionShort": "U",