Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/app/scopes/flows/selected_data_source_scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ class SelectedDataSourceScope extends AutoRouter {
..subscribeToRightDoor()
..subscribeToWindscreenWipers(),
),
BlocProvider(
create: (context) => SuspensionControlBloc(
dataSource: context.read(),
)..add(const SuspensionControlEvent.getMode()),
),
BlocProvider(
create: (context) {
context.read<OutgoingPackagesCubit>().subscribeTo(
Expand Down
63 changes: 63 additions & 0 deletions lib/data/services/data_source/demo_data_source.dart
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ class DemoDataSource extends DataSource
timer = Timer.periodic(
Duration(milliseconds: updatePeriod),
(timer) async {
if (subscriptionParameters.isEmpty) return;
final innerTimerPeriod =
(updatePeriod / subscriptionParameters.length).floor();
for (var i = 0; i < subscriptionParameters.length; i++) {
Expand Down Expand Up @@ -582,6 +583,68 @@ class DemoDataSource extends DataSource
};
await _sendDoorToggleResultCallback(functionId);

return const Result.value(null);
},
),
MainEcuMockResponseWrapper(
ids: {
const DataSourceParameterId.suspensionMode(),
},
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',
);

await manager.updateCallback(
id,
SetUint8ResultBody(
success: !generateRandomErrors() || randomBool,
value: (generateRandomErrors() ||
requestType == FunctionId.requestValue.value)
? SuspensionMode.random.id
: package?.data.last ?? 0,
),
version,
);

return const Result.value(null);
},
),
MainEcuMockResponseWrapper(
ids: {
const DataSourceParameterId.suspensionValue(),
},
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',
);

await manager.updateCallback(
id,
SetUint8ResultBody(
success: !generateRandomErrors() || randomBool,
value: (generateRandomErrors() ||
requestType == FunctionId.requestValue.value)
? Random().nextInt(SuspensionMode.kMaxManualValue)
: package?.data.last ?? 0,
),
version,
);

return const Result.value(null);
},
),
Expand Down
11 changes: 7 additions & 4 deletions lib/domain/data_source/blocs/change_gear_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import 'package:re_seedwork/re_seedwork.dart';
part 'change_gear_bloc.freezed.dart';

@freezed
class ChangeGearEvent extends EffectEvent with _$ChangeGearEvent {
class ChangeGearEvent with _$ChangeGearEvent {
const factory ChangeGearEvent.change(MotorGear newGear) = _Change;
}

typedef ChangeGearState = AsyncData<MotorGear, Object>;

class ChangeGearBloc extends Bloc<ChangeGearEvent, ChangeGearState>
with BlocEventHandlerMixin {
class ChangeGearBloc extends Bloc<ChangeGearEvent, ChangeGearState> {
ChangeGearBloc({
required this.dataSource,
required this.generalDataCubit,
this.responseTimeout = const Duration(seconds: 2),
}) : super(const ChangeGearState.initial(MotorGear.unknown)) {
on<_Change>(_onChange);
}
Expand All @@ -37,6 +37,9 @@ class ChangeGearBloc extends Bloc<ChangeGearEvent, ChangeGearState>
@protected
final GeneralDataCubit generalDataCubit;

@visibleForTesting
final Duration responseTimeout;

Future<void> _onChange(
_Change event,
Emitter<AsyncData<MotorGear, Object>> emit,
Expand All @@ -46,7 +49,7 @@ class ChangeGearBloc extends Bloc<ChangeGearEvent, ChangeGearState>
try {
final future = generalDataCubit.stream
.firstWhere((element) => element.mergedGear == event.newGear)
.timeout(const Duration(seconds: 2));
.timeout(responseTimeout);
for (final parameterId in kParameterIds) {
final res = await dataSource.sendPackage(
OutgoingSetValuePackage(
Expand Down
260 changes: 260 additions & 0 deletions lib/domain/data_source/blocs/suspension_control_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
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/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 'suspension_control_bloc.freezed.dart';

@freezed
class SuspensionControlEvent extends EffectEvent with _$SuspensionControlEvent {
const factory SuspensionControlEvent.switchMode(SuspensionMode mode) =
_SwitchMode;
const factory SuspensionControlEvent.getManual() = _GetManual;
const factory SuspensionControlEvent.setManual(int value) = _SetManual;
const factory SuspensionControlEvent.getMode() = _GetMode;
}

typedef SuspensionControlState
= AsyncData<SuspensionMode, SuspensionControlEvent>;

class SuspensionControlBloc
extends Bloc<SuspensionControlEvent, SuspensionControlState> {
SuspensionControlBloc({
required this.dataSource,
this.responseTimeout = const Duration(seconds: 2),
}) : super(
const SuspensionControlState.initial(
SuspensionMode.manualMiddle(),
),
) {
on<_GetMode>(_onGetMode);
on<_SwitchMode>(_onSwitchMode);
on<_GetManual>(_onGetManualValue);
on<_SetManual>(_onSetManualValue);
}

@protected
final DataSource dataSource;

@visibleForTesting
final Duration responseTimeout;

Future<void> _onGetMode(
_GetMode event,
Emitter<AsyncData<SuspensionMode, SuspensionControlEvent>> emit,
) async {
emit(state.inLoading());

try {
await dataSource.packageStream
.waitFor<SuspensionModeIncomingDataSourcePackage>(
action: () async {
final result = await dataSource.sendPackage(
OutgoingValueRequestPackage(
parameterId: const DataSourceParameterId.suspensionMode(),
),
);

if (result.isError) {
emit(state.inFailure(event));
}
return result.isError;
},
onDone: (package) async {
emit(
package.dataModel.when(
success: (value) {
return AsyncData.success(SuspensionMode.fromId(value));
},
error: () => state.inFailure(event),
),
);
},
timeout: responseTimeout,
);
} catch (e) {
emit(state.inFailure(event));

rethrow;
}

if (state.isSuccess && state.value.isManual) {
add(const SuspensionControlEvent.getManual());
}
}

Future<void> _onGetManualValue(
_GetManual event,
Emitter<AsyncData<SuspensionMode, SuspensionControlEvent>> emit,
) async {
emit(const AsyncData.loading(SuspensionMode.manualMiddle()));

try {
await dataSource.packageStream
.waitFor<SuspensionManualValueIncomingDataSourcePackage>(
action: () async {
final result = await dataSource.sendPackage(
OutgoingValueRequestPackage(
parameterId: const DataSourceParameterId.suspensionValue(),
),
);

if (result.isError) {
emit(state.inFailure(event));
}
return result.isError;
},
onDone: (package) async {
emit(
package.dataModel.when(
success: (value) {
return AsyncData.success(SuspensionMode.manual(value: value));
},
error: () => state.inFailure(event),
),
);
},
timeout: responseTimeout,
);
} catch (e) {
emit(state.inFailure(event));

rethrow;
}
}

Future<void> _onSwitchMode(
_SwitchMode event,
Emitter<AsyncData<SuspensionMode, SuspensionControlEvent>> emit,
) async {
final beforeMode = state.payload;
emit(AsyncData.loading(event.mode));

try {
await dataSource.packageStream
.waitFor<SuspensionModeIncomingDataSourcePackage>(
action: () async {
final result = await dataSource.sendPackage(
OutgoingSetValuePackage(
parameterId: const DataSourceParameterId.suspensionMode(),
setValueBody: SetUint8Body(value: event.mode.id),
),
);

if (result.isError) {
emit(AsyncData.failure(beforeMode, event));
}
return result.isError;
},
onDone: (package) async {
emit(
package.dataModel.when(
success: (value) {
if (value == state.payload.id) {
return state.inSuccess();
}
return AsyncData.failure(
beforeMode,
event,
);
},
error: () => AsyncData.failure(
beforeMode,
event,
),
),
);
},
timeout: responseTimeout,
);
} catch (e) {
emit(AsyncData.failure(beforeMode, event));

rethrow;
}

if (state.isSuccess && state.payload.isManual) {
add(const SuspensionControlEvent.getManual());
}
}

Future<void> _onSetManualValue(
_SetManual event,
Emitter<AsyncData<SuspensionMode, SuspensionControlEvent>> emit,
) async {
final beforeMode = state.payload;
emit(AsyncData.loading(SuspensionMode.manual(value: event.value)));

try {
await dataSource.packageStream
.waitFor<SuspensionManualValueIncomingDataSourcePackage>(
action: () async {
final result = await dataSource.sendPackage(
OutgoingSetValuePackage(
parameterId: const DataSourceParameterId.suspensionValue(),
setValueBody: SetUint8Body(value: event.value),
),
);

if (result.isError) {
emit(
AsyncData.failure(
beforeMode,
event,
),
);
}
return result.isError;
},
onDone: (package) async {
emit(
package.dataModel.when(
success: (value) {
if (value == event.value) {
return state.inSuccess();
}
return AsyncData.failure(
beforeMode,
event,
);
},
error: () => AsyncData.failure(
beforeMode,
event,
),
),
);
},
timeout: responseTimeout,
);
} catch (e) {
emit(
AsyncData.failure(
beforeMode,
event,
),
);

rethrow;
}
}
}

extension on Stream<dynamic> {
Future<void> waitFor<T>({
required Future<bool> Function() action,
required Future<void> 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);
}
}
Loading