From 80568574fa665ecbf1517b89d6efbee4f06ae619 Mon Sep 17 00:00:00 2001 From: Radomir Epur Date: Wed, 12 Feb 2025 18:13:31 +0300 Subject: [PATCH] feat: switch gear button(closes #98) --- .../flows/selected_data_source_scope.dart | 6 + .../data_source/demo_data_source.dart | 96 +++++++++----- .../data_source/blocs/change_gear_bloc.dart | 70 ++++++++++ lib/domain/data_source/data_source.dart | 1 + .../models/data_source_parameter_id.dart | 105 +++++---------- .../implementations/motor_gear_and_roll.dart | 7 + lib/l10n/arb/app_en.arb | 2 + lib/l10n/arb/app_ru.arb | 2 + lib/presentation/app/colors.dart | 5 + lib/presentation/app/extensions.dart | 15 +++ lib/presentation/routes/main_router.dart | 30 ++++- .../subroutes/developer_tools_route.dart | 12 +- .../routes/subroutes/home_route.dart | 17 +-- .../subroutes/select_data_source_route.dart | 4 +- .../routes/subroutes/settings_route.dart | 4 +- .../screens/general/general_screen.dart | 1 - .../general/widgets/change_gear_dialog.dart | 110 ++++++++++++++++ .../general/widgets/gear_symbol_widget.dart | 45 +++++++ .../screens/general/widgets/gear_widget.dart | 122 ++++++++++++++---- .../screens/motor/motor_screen.dart | 13 +- .../widgets/app/organisms/screen_data.dart | 8 +- 21 files changed, 495 insertions(+), 180 deletions(-) create mode 100644 lib/domain/data_source/blocs/change_gear_bloc.dart create mode 100644 lib/presentation/screens/general/widgets/change_gear_dialog.dart create mode 100644 lib/presentation/screens/general/widgets/gear_symbol_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 6d448fb..5d3e2b2 100644 --- a/lib/app/scopes/flows/selected_data_source_scope.dart +++ b/lib/app/scopes/flows/selected_data_source_scope.dart @@ -187,6 +187,12 @@ class SelectedDataSourceScope extends AutoRouter { ); }, ), + BlocProvider( + create: (context) => ChangeGearBloc( + dataSource: context.read(), + generalDataCubit: context.read(), + ), + ), 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 2543e76..6403685 100644 --- a/lib/data/services/data_source/demo_data_source.dart +++ b/lib/data/services/data_source/demo_data_source.dart @@ -364,15 +364,43 @@ class DemoDataSource extends DataSource const DataSourceParameterId.gearAndRoll4(), }, respondCallback: (id, version, manager, [package]) { - final randomGear = - MotorGear.values[Random().nextInt(MotorGear.values.length)]; - final randomRoll = MotorRollDirection - .values[Random().nextInt(MotorRollDirection.values.length)]; return manager.updateCallback( id, MotorGearAndRoll( - gear: randomGear, - rollDirection: randomRoll, + // gear: MotorGear.random, + gear: MotorGear.drive, + rollDirection: MotorRollDirection.random, + status: _getRandomStatus, + ), + version, + ); + }, + ), + MainEcuMockResponseWrapper( + ids: { + const DataSourceParameterId.transmission1(), + const DataSourceParameterId.transmission2(), + const DataSourceParameterId.transmission3(), + const DataSourceParameterId.transmission4(), + }, + unavailableForSubscriptionIds: {}, + respondCallback: (id, version, manager, [package]) { + final expectedGearId = package.checkNotNull('Packet').data.last; + manager.updateCallback( + id, + Uint8WithStatusBody( + value: expectedGearId, + status: PeriodicValueStatus.normal, + ), + version, + ); + final gap = const DataSourceParameterId.transmission1().value - + const DataSourceParameterId.gearAndRoll1().value; + return manager.updateCallback( + DataSourceParameterId.fromInt(id.value - gap), + MotorGearAndRoll( + gear: MotorGear.fromId(expectedGearId), + rollDirection: MotorRollDirection.random, status: _getRandomStatus, ), version, @@ -520,14 +548,14 @@ class DemoDataSource extends DataSource return const Result.value(null); }, ), - MainEcuMockResponseWrapper( - ids: {const CustomImageParameterId()}, - respondCallback: (id, version, manager, [package]) async { - await _sendSetUint8ResultCallback(id, version, package?.data[1]); - - return const Result.value(null); - }, - ), + // MainEcuMockResponseWrapper( + // ids: {const CustomImageParameterId()}, + // respondCallback: (id, version, manager, [package]) async { + // await _sendSetUint8ResultCallback(id, version, package?.data[1]); + + // return const Result.value(null); + // }, + // ), MainEcuMockResponseUpdateCallbackWrapper( ids: {const CustomParameterId(0x00E0)}, convertible: PlainBytesConvertible( @@ -598,26 +626,26 @@ class DemoDataSource extends DataSource ); } - Future _sendSetUint8ResultCallback( - DataSourceParameterId parameterId, - DataSourceProtocolVersion version, [ - int? requiredResult, - ]) async { - await Future.delayed(const Duration(milliseconds: 100)); - final _requiredResult = requiredResult ?? randomUint8; - - await _updateValueCallback( - parameterId, - SuccessEventUint8Body( - generateRandomErrors() - ? randomBool - ? randomUint8 - : _requiredResult - : _requiredResult, - ), - version, - ); - } + // Future _sendSetUint8ResultCallback( + // DataSourceParameterId parameterId, + // DataSourceProtocolVersion version, [ + // int? requiredResult, + // ]) async { + // await Future.delayed(const Duration(milliseconds: 100)); + // final _requiredResult = requiredResult ?? randomUint8; + + // await _updateValueCallback( + // parameterId, + // SuccessEventUint8Body( + // generateRandomErrors() + // ? randomBool + // ? randomUint8 + // : _requiredResult + // : _requiredResult, + // ), + // version, + // ); + // } void _sendPackage(DataSourceIncomingPackage package) { if (controller.isClosed) return; diff --git a/lib/domain/data_source/blocs/change_gear_bloc.dart b/lib/domain/data_source/blocs/change_gear_bloc.dart new file mode 100644 index 0000000..2fc0a62 --- /dev/null +++ b/lib/domain/data_source/blocs/change_gear_bloc.dart @@ -0,0 +1,70 @@ +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_gear_bloc.freezed.dart'; + +@freezed +class ChangeGearEvent extends EffectEvent with _$ChangeGearEvent { + const factory ChangeGearEvent.change(MotorGear newGear) = _Change; +} + +typedef ChangeGearState = AsyncData; + +class ChangeGearBloc extends Bloc + with BlocEventHandlerMixin { + ChangeGearBloc({ + required this.dataSource, + required this.generalDataCubit, + }) : super(const ChangeGearState.initial(MotorGear.unknown)) { + on<_Change>(_onChange); + } + + @visibleForTesting + static const List kParameterIds = [ + DataSourceParameterId.transmission1(), + DataSourceParameterId.transmission2(), + DataSourceParameterId.transmission3(), + DataSourceParameterId.transmission4(), + ]; + + @protected + final DataSource dataSource; + + @protected + final GeneralDataCubit generalDataCubit; + + Future _onChange( + _Change event, + Emitter> emit, + ) async { + emit(AsyncData.loading(event.newGear)); + + try { + final future = generalDataCubit.stream + .firstWhere((element) => element.mergedGear == event.newGear) + .timeout(const Duration(seconds: 2)); + for (final parameterId in kParameterIds) { + final res = await dataSource.sendPackage( + OutgoingSetValuePackage( + parameterId: parameterId, + setValueBody: SetInt8Body(value: event.newGear.id), + ), + ); + if (res.isError) { + return emit(AsyncData.failure(generalDataCubit.state.mergedGear)); + } + } + await future; + + emit(AsyncData.success(event.newGear)); + } on Object catch (_) { + emit(AsyncData.failure(generalDataCubit.state.mergedGear)); + + rethrow; + } + } +} diff --git a/lib/domain/data_source/data_source.dart b/lib/domain/data_source/data_source.dart index 115d1c4..d233b71 100644 --- a/lib/domain/data_source/data_source.dart +++ b/lib/domain/data_source/data_source.dart @@ -1,5 +1,6 @@ // blocs export 'blocs/battery_data_cubit.dart'; +export 'blocs/change_gear_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 47dd5a6..174a0e6 100644 --- a/lib/domain/data_source/models/data_source_parameter_id.dart +++ b/lib/domain/data_source/models/data_source_parameter_id.dart @@ -24,60 +24,49 @@ abstract class DataSourceParameterId { const factory DataSourceParameterId.lowVoltageMinMaxDelta1() = LowVoltageMinMaxDelta1ParameterId; - const factory DataSourceParameterId.lowVoltageMinMaxDelta2() = LowVoltageMinMaxDelta2ParameterId; const factory DataSourceParameterId.highVoltage1() = HighVoltage1ParameterId; - const factory DataSourceParameterId.highVoltage2() = HighVoltage2ParameterId; const factory DataSourceParameterId.highCurrent1() = HighCurrent1ParameterId; - const factory DataSourceParameterId.highCurrent2() = HighCurrent2ParameterId; const factory DataSourceParameterId.maxTemperature1() = MaxTemperature1ParameterId; - const factory DataSourceParameterId.maxTemperature2() = MaxTemperature2ParameterId; const factory DataSourceParameterId.batteryPercent1() = BatteryPercent1ParameterId; - const factory DataSourceParameterId.batteryPercent2() = BatteryPercent2ParameterId; const factory DataSourceParameterId.custom(int id) = CustomParameterId; const factory DataSourceParameterId.temperature1() = Temperature1ParameterId; - const factory DataSourceParameterId.temperature2() = Temperature2ParameterId; // const factory DataSourceParameterId.lowVoltage1() = LowVoltage1ParameterId; - const factory DataSourceParameterId.lowVoltage2() = LowVoltage2ParameterId; const factory DataSourceParameterId.batteryPower1() = BatteryPower1ParameterId; - const factory DataSourceParameterId.batteryPower2() = BatteryPower2ParameterId; // const factory DataSourceParameterId.frontSideBeam() = FrontSideBeamParameterId; - const factory DataSourceParameterId.tailSideBeam() = TailSideBeamParameterId; const factory DataSourceParameterId.lowBeam() = LowBeamParameterId; - const factory DataSourceParameterId.highBeam() = HighBeamParameterId; const factory DataSourceParameterId.frontHazardBeam() = FrontHazardBeamParameterId; - const factory DataSourceParameterId.tailHazardBeam() = TailHazardBeamParameterId; @@ -86,13 +75,10 @@ abstract class DataSourceParameterId { const factory DataSourceParameterId.frontLeftTurnSignal() = FrontLeftTurnSignalParameterId; - const factory DataSourceParameterId.frontRightTurnSignal() = FrontRightTurnSignalParameterId; - const factory DataSourceParameterId.tailLeftTurnSignal() = TailLeftTurnSignalParameterId; - const factory DataSourceParameterId.tailRightTurnSignal() = TailRightTurnSignalParameterId; @@ -104,42 +90,30 @@ abstract class DataSourceParameterId { // const factory DataSourceParameterId.rpm1() = RPM1ParameterId; - const factory DataSourceParameterId.rpm2() = RPM2ParameterId; - const factory DataSourceParameterId.rpm3() = RPM3ParameterId; - const factory DataSourceParameterId.rpm4() = RPM4ParameterId; const factory DataSourceParameterId.motorSpeed1() = MotorSpeed1ParameterId; - const factory DataSourceParameterId.motorSpeed2() = MotorSpeed2ParameterId; - const factory DataSourceParameterId.motorSpeed3() = MotorSpeed3ParameterId; - const factory DataSourceParameterId.motorSpeed4() = MotorSpeed4ParameterId; const factory DataSourceParameterId.motorVoltage1() = MotorVoltage1ParameterId; - const factory DataSourceParameterId.motorVoltage2() = MotorVoltage2ParameterId; - const factory DataSourceParameterId.motorVoltage3() = MotorVoltage3ParameterId; - const factory DataSourceParameterId.motorVoltage4() = MotorVoltage4ParameterId; const factory DataSourceParameterId.motorCurrent1() = MotorCurrent1ParameterId; - const factory DataSourceParameterId.motorCurrent2() = MotorCurrent2ParameterId; - const factory DataSourceParameterId.motorCurrent3() = MotorCurrent3ParameterId; - const factory DataSourceParameterId.motorCurrent4() = MotorCurrent4ParameterId; @@ -149,34 +123,34 @@ abstract class DataSourceParameterId { const factory DataSourceParameterId.motorPower4() = MotorPower4ParameterId; const factory DataSourceParameterId.gearAndRoll1() = GearAndRoll1ParameterId; - const factory DataSourceParameterId.gearAndRoll2() = GearAndRoll2ParameterId; - const factory DataSourceParameterId.gearAndRoll3() = GearAndRoll3ParameterId; - const factory DataSourceParameterId.gearAndRoll4() = GearAndRoll4ParameterId; + const factory DataSourceParameterId.transmission1() = + Transmission1ParameterId; + const factory DataSourceParameterId.transmission2() = + Transmission2ParameterId; + const factory DataSourceParameterId.transmission3() = + Transmission3ParameterId; + const factory DataSourceParameterId.transmission4() = + Transmission4ParameterId; + const factory DataSourceParameterId.motorTemperature1() = MotorTemperature1ParameterId; - const factory DataSourceParameterId.motorTemperature2() = MotorTemperature2ParameterId; - const factory DataSourceParameterId.motorTemperature3() = MotorTemperature3ParameterId; - const factory DataSourceParameterId.motorTemperature4() = MotorTemperature4ParameterId; const factory DataSourceParameterId.controllerTemperature1() = ControllerTemperature1ParameterId; - const factory DataSourceParameterId.controllerTemperature2() = ControllerTemperature2ParameterId; - const factory DataSourceParameterId.controllerTemperature3() = ControllerTemperature3ParameterId; - const factory DataSourceParameterId.controllerTemperature4() = ControllerTemperature4ParameterId; @@ -187,7 +161,6 @@ abstract class DataSourceParameterId { const factory DataSourceParameterId.hood() = HoodParameterId; const factory DataSourceParameterId.leftDoor() = LeftDoorParameterId; - const factory DataSourceParameterId.rightDoor() = RightDoorParameterId; const factory DataSourceParameterId.cabinLight() = CabinLightParameterId; @@ -207,60 +180,46 @@ abstract class DataSourceParameterId { bool get isLowVoltageMinMaxDelta1 => this is LowVoltageMinMaxDelta1ParameterId; - bool get isLowVoltageMinMaxDelta2 => this is LowVoltageMinMaxDelta2ParameterId; bool get isHighVoltage1 => this is HighVoltage1ParameterId; - bool get isHighVoltage2 => this is HighVoltage2ParameterId; bool get isHighCurrent1 => this is HighCurrent1ParameterId; - bool get isHighCurrent2 => this is HighCurrent2ParameterId; bool get isMaxTemperature1 => this is MaxTemperature1ParameterId; - bool get isMaxTemperature2 => this is MaxTemperature2ParameterId; bool get isBatteryPercent1 => this is BatteryPercent1ParameterId; - bool get isBatteryPercent2 => this is BatteryPercent2ParameterId; bool get isTemperature1 => this is Temperature1ParameterId; - bool get isTemperature2 => this is Temperature2ParameterId; // bool get isLowVoltage1 => this is LowVoltage1ParameterId; - bool get isLowVoltage2 => this is LowVoltage2ParameterId; bool get isBatteryPower1 => this is BatteryPower1ParameterId; - bool get isBatteryPower2 => this is BatteryPower2ParameterId; bool get isFrontSideBeam => this is FrontSideBeamParameterId; - bool get isTailSideBeam => this is TailSideBeamParameterId; bool get isLowBeam => this is LowBeamParameterId; - bool get isHighBeam => this is HighBeamParameterId; bool get isFrontHazardBeam => this is FrontHazardBeamParameterId; - bool get isTailHazardBeam => this is TailHazardBeamParameterId; bool get isTailCustomBeam => this is TailCustomBeamParameterId; bool get isFrontLeftTurnSignal => this is FrontLeftTurnSignalParameterId; - bool get isFrontRightTurnSignal => this is FrontRightTurnSignalParameterId; - bool get isTailLeftTurnSignal => this is TailLeftTurnSignalParameterId; - bool get isTailRightTurnSignal => this is TailRightTurnSignalParameterId; bool get isBrakeLight => this is BrakeLightParameterId; @@ -271,35 +230,23 @@ abstract class DataSourceParameterId { // bool get isRPM1 => this is RPM1ParameterId; - bool get isRPM2 => this is RPM2ParameterId; - bool get isRPM3 => this is RPM3ParameterId; - bool get isRPM4 => this is RPM4ParameterId; bool get isMotorSpeed1 => this is MotorSpeed1ParameterId; - bool get isMotorSpeed2 => this is MotorSpeed2ParameterId; - bool get isMotorSpeed3 => this is MotorSpeed3ParameterId; - bool get isMotorSpeed4 => this is MotorSpeed4ParameterId; bool get isMotorVoltage1 => this is MotorVoltage1ParameterId; - bool get isMotorVoltage2 => this is MotorVoltage2ParameterId; - bool get isMotorVoltage3 => this is MotorVoltage3ParameterId; - bool get isMotorVoltage4 => this is MotorVoltage4ParameterId; bool get isMotorCurrent1 => this is MotorCurrent1ParameterId; - bool get isMotorCurrent2 => this is MotorCurrent2ParameterId; - bool get isMotorCurrent3 => this is MotorCurrent3ParameterId; - bool get isMotorCurrent4 => this is MotorCurrent4ParameterId; bool get isMotorPower1 => this is MotorPower1ParameterId; @@ -308,30 +255,26 @@ abstract class DataSourceParameterId { bool get isMotorPower4 => this is MotorPower4ParameterId; bool get isGearAndRoll1 => this is GearAndRoll1ParameterId; - bool get isGearAndRoll2 => this is GearAndRoll2ParameterId; - bool get isGearAndRoll3 => this is GearAndRoll3ParameterId; - bool get isGearAndRoll4 => this is GearAndRoll4ParameterId; - bool get isMotorTemperature1 => this is MotorTemperature1ParameterId; + bool get isTransmission1 => this is Transmission1ParameterId; + bool get isTransmission2 => this is Transmission2ParameterId; + bool get isTransmission3 => this is Transmission3ParameterId; + bool get isTransmission4 => this is Transmission4ParameterId; + bool get isMotorTemperature1 => this is MotorTemperature1ParameterId; bool get isMotorTemperature2 => this is MotorTemperature2ParameterId; - bool get isMotorTemperature3 => this is MotorTemperature3ParameterId; - bool get isMotorTemperature4 => this is MotorTemperature4ParameterId; bool get isControllerTemperature1 => this is ControllerTemperature1ParameterId; - bool get isControllerTemperature2 => this is ControllerTemperature2ParameterId; - bool get isControllerTemperature3 => this is ControllerTemperature3ParameterId; - bool get isControllerTemperature4 => this is ControllerTemperature4ParameterId; @@ -417,6 +360,10 @@ abstract class DataSourceParameterId { DataSourceParameterId.gearAndRoll2(), DataSourceParameterId.gearAndRoll3(), DataSourceParameterId.gearAndRoll4(), + DataSourceParameterId.transmission1(), + DataSourceParameterId.transmission2(), + DataSourceParameterId.transmission3(), + DataSourceParameterId.transmission4(), DataSourceParameterId.motorTemperature1(), DataSourceParameterId.motorTemperature2(), DataSourceParameterId.motorTemperature3(), @@ -697,6 +644,22 @@ class GearAndRoll4ParameterId extends DataSourceParameterId { const GearAndRoll4ParameterId() : super(0x0149); } +class Transmission1ParameterId extends DataSourceParameterId { + const Transmission1ParameterId() : super(0x0106); +} + +class Transmission2ParameterId extends DataSourceParameterId { + const Transmission2ParameterId() : super(0x0107); +} + +class Transmission3ParameterId extends DataSourceParameterId { + const Transmission3ParameterId() : super(0x0136); +} + +class Transmission4ParameterId extends DataSourceParameterId { + const Transmission4ParameterId() : super(0x0137); +} + class MotorTemperature1ParameterId extends DataSourceParameterId { const MotorTemperature1ParameterId() : super(0x011A); } 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 3618b8b..fd7f40a 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,3 +1,5 @@ +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'; @@ -37,6 +39,8 @@ enum MotorGear { MotorGear.unknown => unknown(), }; } + + static MotorGear get random => values[Random().nextInt(values.length)]; } enum MotorRollDirection { @@ -68,6 +72,9 @@ enum MotorRollDirection { MotorRollDirection.unknown => unknown(), }; } + + static MotorRollDirection get random => + values[Random().nextInt(values.length)]; } class MotorGearAndRoll extends IntBytesConvertibleWithStatus { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 065396a..960db92 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -288,6 +288,8 @@ "lowGearShort": "L", "boostGearShort": "B", "unknownGearShort": "U", + "errorSwitchingGearMessage": "Error switching the gear", + "stopBeforeSwitchingGearMessage": "To switch the gear, you must first stop", "reverseMotorRollDirectionShort": "R", "unknownMotorRollDirectionShort": "U", "forwardMotorRollDirectionShort": "F", diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 154d52d..7791bb8 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -288,6 +288,8 @@ "lowGearShort": "L", "boostGearShort": "B", "unknownGearShort": "U", + "errorSwitchingGearMessage": "Ошибка переключения передачи", + "stopBeforeSwitchingGearMessage": "Для переключения передачи нужно сначала остановиться", "reverseMotorRollDirectionShort": "R", "unknownMotorRollDirectionShort": "U", "forwardMotorRollDirectionShort": "F", diff --git a/lib/presentation/app/colors.dart b/lib/presentation/app/colors.dart index 007bf03..2e412cb 100644 --- a/lib/presentation/app/colors.dart +++ b/lib/presentation/app/colors.dart @@ -56,6 +56,8 @@ abstract class AppColorsData with EquatableMixin { Color get borderAccent; + Color get dialogBarrier; + @override List get props => [brightness]; } @@ -132,6 +134,9 @@ class _AppColorsDataDark extends AppColorsData { @override Color get borderAccent => const Color(0xFF546E7A); + + @override + Color get dialogBarrier => const Color(0x66000000); } class AppColors extends StatelessWidget { diff --git a/lib/presentation/app/extensions.dart b/lib/presentation/app/extensions.dart index a9cbbee..3484df1 100644 --- a/lib/presentation/app/extensions.dart +++ b/lib/presentation/app/extensions.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:package_info_plus/package_info_plus.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'; extension PeriodicStatusExtension on BuildContext { @@ -42,3 +43,17 @@ extension FlexSizeExtension on num { return value; } } + +extension GearToTextExtension on BuildContext { + String gearToShortString(MotorGear? gear) { + if (gear == null) return l10n.unknownGearShort; + return gear.when( + reverse: () => l10n.reverseGearShort, + neutral: () => l10n.neutralGearShort, + drive: () => l10n.driveGearShort, + low: () => l10n.lowGearShort, + boost: () => l10n.boostGearShort, + unknown: () => l10n.unknownGearShort, + ); + } +} diff --git a/lib/presentation/routes/main_router.dart b/lib/presentation/routes/main_router.dart index d452b04..cd2406c 100644 --- a/lib/presentation/routes/main_router.dart +++ b/lib/presentation/routes/main_router.dart @@ -11,8 +11,10 @@ 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'; import 'package:pixel_app_flutter/presentation/screens/apps/apps_screen.dart'; import 'package:pixel_app_flutter/presentation/screens/car_info/car_info_screen.dart'; import 'package:pixel_app_flutter/presentation/screens/common/loading_screen.dart'; @@ -28,6 +30,7 @@ import 'package:pixel_app_flutter/presentation/screens/developer_tools/requests_ import 'package:pixel_app_flutter/presentation/screens/developer_tools/widgets/integer_list_dialog.dart'; 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/led_panel_switcher_dialog.dart'; import 'package:pixel_app_flutter/presentation/screens/home/home_screen.dart'; import 'package:pixel_app_flutter/presentation/screens/navigator/navigator_screen.dart'; @@ -78,6 +81,12 @@ class MainRouter extends RootStackRouter { _userDefinedButtonsRoute, // _developerToolsRoute(), + // + CustomRoute( + path: 'change-gear-dialog', + page: ChangeGearDialogRoute.page, + customRouteBuilder: dialogRouteBuilder, + ), ], ), // @@ -97,14 +106,25 @@ class MainRouter extends RootStackRouter { Route dialogRouteBuilder( BuildContext context, Widget child, - AutoRoutePage page, { - Color? barrierColor, -}) { + AutoRoutePage page, +) { + return DialogRoute( + settings: page, + builder: (context) => child, + context: context, + barrierColor: context.colors.dialogBarrier, + ); +} + +Route noBarrierDialogRouteBuilder( + BuildContext context, + Widget child, + AutoRoutePage page, +) { return DialogRoute( settings: page, builder: (context) => child, context: context, - barrierColor: barrierColor, - // expanded: true, + barrierColor: null, ); } diff --git a/lib/presentation/routes/subroutes/developer_tools_route.dart b/lib/presentation/routes/subroutes/developer_tools_route.dart index 43cf2f8..1c1e1d6 100644 --- a/lib/presentation/routes/subroutes/developer_tools_route.dart +++ b/lib/presentation/routes/subroutes/developer_tools_route.dart @@ -44,34 +44,34 @@ AutoRoute _developerToolsRoute({bool selectedDS = true}) => AutoRoute( CustomRoute>( page: FilterParameterIdDialogRoute.page, path: 'parameter-id', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), CustomRoute>( page: FilterRequestTypeDialogRoute.page, path: 'request-type', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), CustomRoute>( page: FilterDirectionDialogRoute.page, path: 'direction', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), ], ), CustomRoute>( page: ChangeParametersSubscriptionDialogRoute.page, path: 'change-parameters-subscription', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), CustomRoute( page: ChangeRequestPeriodDialogRoute.page, path: 'change-request-period', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), CustomRoute( page: ChangeHandshakeResponseTimeoutDialogRoute.page, path: 'change-handshake-response-timeout', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), ], ); diff --git a/lib/presentation/routes/subroutes/home_route.dart b/lib/presentation/routes/subroutes/home_route.dart index 80fc599..3a1adad 100644 --- a/lib/presentation/routes/subroutes/home_route.dart +++ b/lib/presentation/routes/subroutes/home_route.dart @@ -15,7 +15,7 @@ final _homeRoute = AutoRoute( CustomRoute( path: 'led-switcher-dialog', page: LEDSwitcherDialogRoute.page, - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), ], ), @@ -34,7 +34,7 @@ final _homeRoute = AutoRoute( CustomRoute( path: 'enable-fast-access', page: EnableFastAccessDialogRoute.page, - customRouteBuilder: enableFastAccessDialofRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), ], ), @@ -59,19 +59,6 @@ final _homeRoute = AutoRoute( ], ); -Route enableFastAccessDialofRouteBuilder( - BuildContext context, - Widget child, - AutoRoutePage page, -) { - return dialogRouteBuilder( - context, - child, - page, - barrierColor: Colors.transparent, - ); -} - @RoutePage(name: 'NavigatorFlow') class NavigatorScope extends AutoRouter { const NavigatorScope({super.key}); diff --git a/lib/presentation/routes/subroutes/select_data_source_route.dart b/lib/presentation/routes/subroutes/select_data_source_route.dart index 5538809..55cceb5 100644 --- a/lib/presentation/routes/subroutes/select_data_source_route.dart +++ b/lib/presentation/routes/subroutes/select_data_source_route.dart @@ -18,12 +18,12 @@ AutoRoute _selectDataSourceRoute({bool root = true}) => AutoRoute( CustomRoute( page: SelectDeviceDialogRoute.page, path: 'select-device', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), CustomRoute( page: DataSourceDisconnectDialogRoute.page, path: 'disconnect', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), ], ), diff --git a/lib/presentation/routes/subroutes/settings_route.dart b/lib/presentation/routes/subroutes/settings_route.dart index 73774ae..a2d97b0 100644 --- a/lib/presentation/routes/subroutes/settings_route.dart +++ b/lib/presentation/routes/subroutes/settings_route.dart @@ -19,12 +19,12 @@ final _settingsRoute = AutoRoute( CustomRoute( page: AddConfigurationDialogRoute.page, path: 'add-configuration', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), CustomRoute( page: RemoveConfigurationDialogRoute.page, path: 'remove-configuration', - customRouteBuilder: dialogRouteBuilder, + customRouteBuilder: noBarrierDialogRouteBuilder, ), ], ), diff --git a/lib/presentation/screens/general/general_screen.dart b/lib/presentation/screens/general/general_screen.dart index 140ff97..804f3c2 100644 --- a/lib/presentation/screens/general/general_screen.dart +++ b/lib/presentation/screens/general/general_screen.dart @@ -119,7 +119,6 @@ class HandsetGeneralScreenBody extends StatelessWidget { IntrinsicHeight( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, children: [ const SpeedWidget(), if (landscape) diff --git a/lib/presentation/screens/general/widgets/change_gear_dialog.dart b/lib/presentation/screens/general/widgets/change_gear_dialog.dart new file mode 100644 index 0000000..6375ccc --- /dev/null +++ b/lib/presentation/screens/general/widgets/change_gear_dialog.dart @@ -0,0 +1,110 @@ +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/gear_symbol_widget.dart'; +import 'package:pixel_app_flutter/presentation/widgets/app/organisms/screen_data.dart'; +import 'package:re_seedwork/re_seedwork.dart'; + +@RoutePage(name: 'ChangeGearDialogRoute') +class ChangeGearDialog extends StatefulWidget { + const ChangeGearDialog({super.key, required this.padding}); + + final (double? left, double? top, double? right, double? bottom) padding; + + @override + State createState() => _ChangeGearDialogState(); +} + +class _ChangeGearDialogState 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 gear in [ + MotorGear.drive, + MotorGear.neutral, + MotorGear.reverse, + ]) + InkWell( + onTap: () { + context.router.maybePop(gear); + }, + borderRadius: kBorderRadius, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: GearSymbolWidget( + gear: gear, + screenSize: size, + ), + ), + ), + ] + .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 new file mode 100644 index 0000000..8cbfe38 --- /dev/null +++ b/lib/presentation/screens/general/widgets/gear_symbol_widget.dart @@ -0,0 +1,45 @@ +import 'package:flutter/widgets.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/app/extensions.dart'; + +class GearSymbolWidget extends StatelessWidget { + const GearSymbolWidget({ + required this.gear, + required this.screenSize, + this.inactive = false, + super.key, + }); + + @protected + final MotorGear gear; + + @protected + final Size screenSize; + + @protected + final bool inactive; + + @protected + static const kTextStyle = TextStyle( + height: 1.2, + fontSize: 50, + fontStyle: FontStyle.normal, + fontWeight: FontWeight.w700, + fontFeatures: [FontFeature.tabularFigures()], + ); + + @override + Widget build(BuildContext context) { + return Text( + context.gearToShortString(gear), + style: kTextStyle.copyWith( + color: inactive ? context.colors.disabled : context.colors.text, + fontSize: screenSize.height.flexSize( + screenFlexRange: (600, 700), + valueClampRange: (50, 60), + ), + ), + ); + } +} diff --git a/lib/presentation/screens/general/widgets/gear_widget.dart b/lib/presentation/screens/general/widgets/gear_widget.dart index 20e43f9..fe08f80 100644 --- a/lib/presentation/screens/general/widgets/gear_widget.dart +++ b/lib/presentation/screens/general/widgets/gear_widget.dart @@ -1,10 +1,16 @@ -import 'package:flutter/widgets.dart'; +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/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/app/colors.dart'; -import 'package:pixel_app_flutter/presentation/app/extensions.dart'; +import 'package:pixel_app_flutter/presentation/routes/main_router.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'; +import 'package:re_widgets/re_widgets.dart'; class GearWidget extends StatelessWidget { const GearWidget({ @@ -16,38 +22,98 @@ class GearWidget extends StatelessWidget { final Size screenSize; @protected - static const kTextStyle = TextStyle( - height: 1.2, - fontSize: 50, - fontStyle: FontStyle.normal, - fontWeight: FontWeight.w700, - fontFeatures: [FontFeature.tabularFigures()], - ); + static const kHorizontalPadding = 10; @override Widget build(BuildContext context) { return BlocSelector( selector: (state) => state.mergedGear, - builder: (context, state) { - final gear = state.when( - reverse: () => context.l10n.reverseGearShort, - neutral: () => context.l10n.neutralGearShort, - drive: () => context.l10n.driveGearShort, - low: () => context.l10n.lowGearShort, - boost: () => context.l10n.boostGearShort, - unknown: () => context.l10n.unknownGearShort, - ); - return Text( - gear, - style: kTextStyle.copyWith( - color: context.colors.text, - fontSize: screenSize.height.flexSize( - screenFlexRange: (600, 700), - valueClampRange: (50, 60), - ), - ), + builder: (context, gear) { + return BlocConsumer( + listenWhen: (previous, current) => current.isFailure, + listener: (context, state) { + context.showSnackBar(context.l10n.errorSwitchingGearMessage); + }, + builder: (context, settingGearState) { + return InkWell( + borderRadius: const BorderRadius.all(Radius.circular(10)), + onTap: settingGearState.isLoading + ? null + : () async { + if (context.read() is! DemoDataSource) { + final speed = context + .read() + .state + .mergedSpeed + .value; + if (speed > 0) { + unawaited( + context.showSnackBar( + context.l10n.stopBeforeSwitchingGearMessage, + ), + ); + return; + } + } + + 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 gear = await context.router.push( + ChangeGearDialogRoute( + padding: padding, + ), + ); + + if (gear == null || !context.mounted) return; + + context + .read() + .add(ChangeGearEvent.change(gear)); + }, + child: GearSymbolWidget( + gear: settingGearState.maybeWhen( + orElse: (payload) => gear, + loading: (payload) => payload, + ), + 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; + } + } +} diff --git a/lib/presentation/screens/motor/motor_screen.dart b/lib/presentation/screens/motor/motor_screen.dart index abad9e0..af9c178 100644 --- a/lib/presentation/screens/motor/motor_screen.dart +++ b/lib/presentation/screens/motor/motor_screen.dart @@ -4,6 +4,7 @@ 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/app/extensions.dart'; import 'package:pixel_app_flutter/presentation/widgets/common/atoms/responsive_padding.dart'; import 'package:pixel_app_flutter/presentation/widgets/common/atoms/sliver_section_subtitle.dart'; import 'package:pixel_app_flutter/presentation/widgets/common/molecules/cell_sliver_grid_builder.dart'; @@ -207,18 +208,6 @@ class MotorScreen extends StatelessWidget { } extension on BuildContext { - String gearToShortString(MotorGear? gear) { - if (gear == null) return l10n.unknownGearShort; - return gear.when( - reverse: () => l10n.reverseGearShort, - neutral: () => l10n.neutralGearShort, - drive: () => l10n.driveGearShort, - low: () => l10n.lowGearShort, - boost: () => l10n.boostGearShort, - unknown: () => l10n.unknownGearShort, - ); - } - String gearToFullString(MotorGear? gear) { if (gear == null) return l10n.unknownGear; return gear.when( diff --git a/lib/presentation/widgets/app/organisms/screen_data.dart b/lib/presentation/widgets/app/organisms/screen_data.dart index c3233a0..847bc11 100644 --- a/lib/presentation/widgets/app/organisms/screen_data.dart +++ b/lib/presentation/widgets/app/organisms/screen_data.dart @@ -69,9 +69,9 @@ class Screen extends StatelessWidget { static ScreenData of(BuildContext context, {bool watch = true}) { final getInheritedElement = context.getElementForInheritedWidgetOfExactType; - final inheritedAdapriveWidget = getInheritedElement(); + final inheritedAdaptiveWidget = getInheritedElement(); - if (inheritedAdapriveWidget == null) { + if (inheritedAdaptiveWidget == null) { throw FlutterError( 'Screen.of(context) called with a context that does not ' 'contain a ScreenData', @@ -79,10 +79,10 @@ class Screen extends StatelessWidget { } if (watch) { - context.dependOnInheritedElement(inheritedAdapriveWidget); + context.dependOnInheritedElement(inheritedAdaptiveWidget); } - return (inheritedAdapriveWidget.widget as InheritedScreenData).data; + return (inheritedAdaptiveWidget.widget as InheritedScreenData).data; } @override