diff --git a/lib/app/scopes/flows/selected_data_source_scope.dart b/lib/app/scopes/flows/selected_data_source_scope.dart index 038531c..c257085 100644 --- a/lib/app/scopes/flows/selected_data_source_scope.dart +++ b/lib/app/scopes/flows/selected_data_source_scope.dart @@ -198,6 +198,11 @@ class SelectedDataSourceScope extends AutoRouter { generalDataCubit: context.read(), ), ), + BlocProvider( + create: (context) => SteeringRackControlBloc( + dataSource: context.read(), + )..add(const SteeringRackControlEvent.get()), + ), BlocProvider( create: (context) => LaunchAppCubit(appsService: context.read()), 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/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 07189e1..22ef9fa 100644 --- a/lib/domain/data_source/data_source.dart +++ b/lib/domain/data_source/data_source.dart @@ -14,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'; @@ -27,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 ba86c69..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,6 +136,8 @@ abstract class DataSourceParameterId { const factory DataSourceParameterId.transmission4() = Transmission4ParameterId; + const factory DataSourceParameterId.steeringRack() = SteeringRackParameterId; + const factory DataSourceParameterId.motorTemperature1() = MotorTemperature1ParameterId; const factory DataSourceParameterId.motorTemperature2() = @@ -269,6 +271,8 @@ abstract class DataSourceParameterId { bool get isTransmission3 => this is Transmission3ParameterId; bool get isTransmission4 => this is Transmission4ParameterId; + bool get isSteeringRack => this is SteeringRackParameterId; + bool get isMotorTemperature1 => this is MotorTemperature1ParameterId; bool get isMotorTemperature2 => this is MotorTemperature2ParameterId; bool get isMotorTemperature3 => this is MotorTemperature3ParameterId; @@ -372,6 +376,7 @@ abstract class DataSourceParameterId { DataSourceParameterId.transmission2(), DataSourceParameterId.transmission3(), DataSourceParameterId.transmission4(), + DataSourceParameterId.steeringRack(), DataSourceParameterId.motorTemperature1(), DataSourceParameterId.motorTemperature2(), DataSourceParameterId.motorTemperature3(), @@ -671,6 +676,10 @@ class Transmission4ParameterId extends DataSourceParameterId { const Transmission4ParameterId() : super(0x0137); } +class SteeringRackParameterId extends DataSourceParameterId { + const SteeringRackParameterId() : super(0x01A4); +} + class MotorTemperature1ParameterId extends DataSourceParameterId { const MotorTemperature1ParameterId() : super(0x011A); } 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_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 906b893..e2c69f2 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -316,6 +316,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 153e4a0..8cb39c1 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -316,6 +316,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 39664c4..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'; @@ -32,6 +31,7 @@ import 'package:pixel_app_flutter/presentation/screens/developer_tools/widgets/s 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/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'; 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 4233c20..8965cce 100644 --- a/lib/presentation/screens/general/general_screen.dart +++ b/lib/presentation/screens/general/general_screen.dart @@ -8,6 +8,7 @@ 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/wipers_switcher_button.dart'; @@ -17,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 { @@ -133,23 +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 ...[ - GearWidget(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/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), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ); + } +}