Skip to content

Commit 9dd85c4

Browse files
authored
feat: add windscreen wipers button (#63)
* fix: wait for package sending through USBDataSource(issue on desktop versions) * chore: change bytes format to hex from int for raw incoming packages(when exporting logs)(resolves #54) * chore: improve log file readability * chore: format hex bytes everywhere to 0xHH format * fix: add returning type to DialogRoutes from DeveloperTools * feat: add windscreen wipers switch button(resolves #62) * chore: increase logs cubit max records number to 500 * chore: v0.15.6+37
1 parent 849c983 commit 9dd85c4

27 files changed

+351
-120
lines changed

lib/app/scopes/flows/selected_data_source_scope.dart

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:math' as math;
2+
13
import 'package:auto_route/auto_route.dart';
24
import 'package:flutter/material.dart';
35
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -6,6 +8,7 @@ import 'package:pixel_app_flutter/bootstrap.dart';
68
import 'package:pixel_app_flutter/domain/app/storages/logger_storage.dart';
79
import 'package:pixel_app_flutter/domain/apps/apps.dart';
810
import 'package:pixel_app_flutter/domain/data_source/data_source.dart';
11+
import 'package:pixel_app_flutter/domain/data_source/extensions/int.dart';
912
import 'package:pixel_app_flutter/domain/developer_tools/developer_tools.dart';
1013
import 'package:pixel_app_flutter/domain/user_defined_buttons/storages/user_defined_buttons_storage.dart';
1114
import 'package:pixel_app_flutter/l10n/l10n.dart';
@@ -21,6 +24,17 @@ class SelectedDataSourceScope extends AutoRouter {
2124
@override
2225
Widget Function(BuildContext context, Widget content)? get builder {
2326
return (context, content) {
27+
String? inValidatedKey;
28+
String? inRawKey;
29+
String? inComposedKey;
30+
String? outKey;
31+
int? maxKeyLength;
32+
33+
int getKeysMaxLength() {
34+
return [inValidatedKey, inRawKey, inComposedKey, outKey]
35+
.fold<int>(0, (pr, curr) => math.max(curr?.length ?? 0, pr));
36+
}
37+
2438
return BlocBuilder<DataSourceCubit, DataSourceState>(
2539
builder: (context, state) {
2640
final dswa = state.ds.when(
@@ -48,6 +62,12 @@ class SelectedDataSourceScope extends AutoRouter {
4862
BlocProvider<DataSourceConnectionStatusCubit>(
4963
create: (context) {
5064
if (context.read<Environment>().isDev) {
65+
final dataSourceKey = context.read<DataSource>().key;
66+
inValidatedKey ??= 'InValidatedPackage($dataSourceKey)';
67+
inRawKey ??= 'InRaw($dataSourceKey)';
68+
inComposedKey ??= 'InComposedPackage($dataSourceKey)';
69+
outKey ??= 'OutPackage($dataSourceKey)';
70+
maxKeyLength ??= getKeysMaxLength();
5171
context.read<DataSource>().addObserver(
5272
(observable) {
5373
observable.whenOrNull(
@@ -59,8 +79,8 @@ class SelectedDataSourceScope extends AutoRouter {
5979
DataSourceRequestDirection.incoming,
6080
);
6181
context.read<LoggerStorage>().logInfo(
62-
package.toString(),
63-
'IncomingProcessedPackage',
82+
package.toString(withDirection: false),
83+
inValidatedKey.padLeftSpace(maxKeyLength),
6484
);
6585
},
6686
outgoingPackage: (package) {
@@ -75,8 +95,8 @@ class SelectedDataSourceScope extends AutoRouter {
7595
DataSourceRequestDirection.outgoing,
7696
);
7797
context.read<LoggerStorage>().logInfo(
78-
package.toString(),
79-
'OutgoingPackage',
98+
package.toString(withDirection: false),
99+
outKey.padLeftSpace(maxKeyLength),
80100
);
81101
},
82102
rawIncomingBytes: (bytes) {
@@ -85,8 +105,8 @@ class SelectedDataSourceScope extends AutoRouter {
85105
DataSourceRequestDirection.incoming,
86106
);
87107
context.read<LoggerStorage>().logInfo(
88-
bytes.toString(),
89-
'IncomingRawBytes',
108+
bytes.toFormattedHexString,
109+
inRawKey.padLeftSpace(maxKeyLength),
90110
);
91111
},
92112
rawIncomingPackage: (bytes) {
@@ -95,8 +115,8 @@ class SelectedDataSourceScope extends AutoRouter {
95115
DataSourceRequestDirection.incoming,
96116
);
97117
context.read<LoggerStorage>().logInfo(
98-
bytes.toString(),
99-
'IncomingRawPackage',
118+
bytes.toFormattedHexString,
119+
inComposedKey.padLeftSpace(maxKeyLength),
100120
);
101121
},
102122
);
@@ -134,11 +154,12 @@ class SelectedDataSourceScope extends AutoRouter {
134154
lazy: false,
135155
),
136156
BlocProvider(
137-
create: (context) => DoorsCubit(
157+
create: (context) => GeneralInterfacesCubit(
138158
dataSource: context.read(),
139159
)
140160
..subscribeToLeftDoor()
141-
..subscribeToRightDoor(),
161+
..subscribeToRightDoor()
162+
..subscribeToWindscreenWipers(),
142163
),
143164
BlocProvider(
144165
create: (context) {
@@ -197,3 +218,7 @@ class SelectedDataSourceScope extends AutoRouter {
197218
};
198219
}
199220
}
221+
222+
extension on String? {
223+
String padLeftSpace(int? width) => this?.padLeft(width ?? 0) ?? '';
224+
}

lib/data/services/data_source/demo_data_source.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,11 @@ class DemoDataSource extends DataSource
375375
const CustomImageParameterId(),
376376
);
377377
})
378+
..voidOn<WindscreenWipersParameterId>(() {
379+
_sendSetBoolUint8ResultCallback(
380+
const WindscreenWipersParameterId(),
381+
);
382+
})
378383
..voidOn<CustomParameterId>(() {
379384
switch (parameterId.value) {
380385
case 0x00E0:
@@ -568,6 +573,12 @@ class DemoDataSource extends DataSource
568573
package.boolData,
569574
);
570575
})
576+
..voidOn<WindscreenWipersParameterId>(() {
577+
_sendSetBoolUint8ResultCallback(
578+
const WindscreenWipersParameterId(),
579+
package.boolData,
580+
);
581+
})
571582
..voidOn<CustomImageParameterId>(() {
572583
_sendSetUint8ResultCallback(
573584
const CustomImageParameterId(),

lib/data/services/data_source/usb_data_source.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class USBDataSource extends DataSource
8383

8484
observeOutgoing(package);
8585

86-
sp.write(package.toUint8List);
86+
sp.write(package.toUint8List, timeout: 0);
8787

8888
return const Result.value(null);
8989
}

lib/domain/data_source/blocs/doors_cubit.dart renamed to lib/domain/data_source/blocs/general_interfaces_cubit.dart

Lines changed: 96 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,68 @@ import 'package:pixel_app_flutter/domain/data_source/models/package/outgoing/out
77
import 'package:pixel_app_flutter/domain/data_source/models/package_data/package_data.dart';
88
import 'package:re_seedwork/re_seedwork.dart';
99

10-
typedef DoorState = AsyncData<bool, ToggleStateError>;
10+
typedef GeneralInterfaceState = AsyncData<bool, ToggleStateError>;
1111

1212
@immutable
13-
class DoorsState with EquatableMixin {
14-
const DoorsState({required this.left, required this.right});
13+
class GeneralInterfacesState with EquatableMixin {
14+
const GeneralInterfacesState({
15+
required this.leftDoor,
16+
required this.rightDoor,
17+
required this.wipers,
18+
});
1519

16-
const DoorsState.initial()
17-
: left = const AsyncData.initial(false),
18-
right = const AsyncData.initial(false);
20+
const GeneralInterfacesState.initial()
21+
: leftDoor = const AsyncData.initial(false),
22+
rightDoor = const AsyncData.initial(false),
23+
wipers = const AsyncData.initial(false);
1924

20-
final DoorState left;
21-
final DoorState right;
25+
final GeneralInterfaceState leftDoor;
26+
final GeneralInterfaceState rightDoor;
27+
final GeneralInterfaceState wipers;
2228

2329
@override
24-
List<Object?> get props => [left, right];
30+
List<Object?> get props => [leftDoor, rightDoor, wipers];
2531

26-
DoorsState copyWith({
27-
DoorState? left,
28-
DoorState? right,
32+
GeneralInterfacesState copyWith({
33+
GeneralInterfaceState? leftDoor,
34+
GeneralInterfaceState? rightDoor,
35+
GeneralInterfaceState? wipers,
2936
}) {
30-
return DoorsState(
31-
left: left ?? this.left,
32-
right: right ?? this.right,
37+
return GeneralInterfacesState(
38+
leftDoor: leftDoor ?? this.leftDoor,
39+
rightDoor: rightDoor ?? this.rightDoor,
40+
wipers: wipers ?? this.wipers,
3341
);
3442
}
3543

36-
bool get hasFailureState => [left, right].any((element) => element.isFailure);
44+
bool get hasFailureState =>
45+
[leftDoor, rightDoor, wipers].any((element) => element.isFailure);
3746

3847
List<R> whenFailure<R>(
39-
DoorsState prevState, {
40-
required R Function(ToggleStateError error) left,
41-
required R Function(ToggleStateError error) right,
48+
GeneralInterfacesState prevState, {
49+
required R Function(ToggleStateError error) leftDoor,
50+
required R Function(ToggleStateError error) rightDoor,
51+
required R Function(ToggleStateError error) wipers,
4252
}) {
43-
final leftError = this.left.error;
44-
final rightError = this.right.error;
53+
final leftError = this.leftDoor.error;
54+
final rightError = this.rightDoor.error;
55+
final wipersError = this.wipers.error;
4556

4657
final errors = [
4758
_filterError(
4859
leftError,
49-
prevState.left.error,
50-
left,
60+
prevState.leftDoor.error,
61+
leftDoor,
5162
),
5263
_filterError(
5364
rightError,
54-
prevState.right.error,
55-
right,
65+
prevState.rightDoor.error,
66+
rightDoor,
67+
),
68+
_filterError(
69+
wipersError,
70+
prevState.wipers.error,
71+
wipers,
5672
),
5773
].whereType<R>().toList();
5874

@@ -78,19 +94,24 @@ extension _ErrorExtension<V, E extends Object> on AsyncData<V, E> {
7894
);
7995
}
8096

81-
class DoorsCubit extends Cubit<DoorsState> with ConsumerBlocMixin {
82-
DoorsCubit({
97+
class GeneralInterfacesCubit extends Cubit<GeneralInterfacesState>
98+
with ConsumerBlocMixin {
99+
GeneralInterfacesCubit({
83100
required this.dataSource,
84101
this.subscriptionTimeout = kSubscriptionTimeout,
85102
this.toggleTimeout = kToggleTimeout,
86-
}) : super(const DoorsState.initial()) {
103+
}) : super(const GeneralInterfacesState.initial()) {
87104
subscribe<DataSourceIncomingPackage>(dataSource.packageStream, (package) {
88105
package
89106
..voidOnModel<DoorBody, LeftDoorIncomingDataSourcePackage>((model) {
90-
emit(state.copyWith(left: AsyncData.success(model.isOpen)));
107+
emit(state.copyWith(leftDoor: AsyncData.success(model.isOpen)));
91108
})
92109
..voidOnModel<DoorBody, RightDoorIncomingDataSourcePackage>((model) {
93-
emit(state.copyWith(right: AsyncData.success(model.isOpen)));
110+
emit(state.copyWith(rightDoor: AsyncData.success(model.isOpen)));
111+
})
112+
..voidOnModel<WindscreenWipersBody,
113+
WindscreenWipersIncomingDataSourcePackage>((model) {
114+
emit(state.copyWith(wipers: AsyncData.success(model.isOn)));
94115
});
95116
});
96117
}
@@ -115,8 +136,8 @@ class DoorsCubit extends Cubit<DoorsState> with ConsumerBlocMixin {
115136
const DataSourceParameterId.custom(ButtonFunctionId.leftDoorId),
116137
]) {
117138
_subscribe(
118-
newStateBuilder: (newState) => state.copyWith(left: newState),
119-
newFeatureStateBuilder: () => state.left,
139+
newStateBuilder: (newState) => state.copyWith(leftDoor: newState),
140+
newFeatureStateBuilder: () => state.leftDoor,
120141
parameterId: parameterId,
121142
);
122143
}
@@ -126,14 +147,25 @@ class DoorsCubit extends Cubit<DoorsState> with ConsumerBlocMixin {
126147
const DataSourceParameterId.custom(ButtonFunctionId.rightDoorId),
127148
]) {
128149
_subscribe(
129-
newStateBuilder: (newState) => state.copyWith(right: newState),
130-
newFeatureStateBuilder: () => state.right,
150+
newStateBuilder: (newState) => state.copyWith(rightDoor: newState),
151+
newFeatureStateBuilder: () => state.rightDoor,
152+
parameterId: parameterId,
153+
);
154+
}
155+
156+
void subscribeToWindscreenWipers({
157+
DataSourceParameterId parameterId =
158+
const DataSourceParameterId.windscreenWipers(),
159+
}) {
160+
_subscribe(
161+
newStateBuilder: (newState) => state.copyWith(wipers: newState),
162+
newFeatureStateBuilder: () => state.wipers,
131163
parameterId: parameterId,
132164
);
133165
}
134166

135167
Future<void> _subscribe({
136-
required DoorsState Function(
168+
required GeneralInterfacesState Function(
137169
AsyncData<bool, ToggleStateError> newState,
138170
) newStateBuilder,
139171
required AsyncData<bool, ToggleStateError> Function()
@@ -148,7 +180,7 @@ class DoorsCubit extends Cubit<DoorsState> with ConsumerBlocMixin {
148180

149181
final failure = await stream
150182
.where((event) => newFeatureStateBuilder().isExecuted)
151-
.map<DoorsState?>((event) => null)
183+
.map<GeneralInterfacesState?>((event) => null)
152184
.first
153185
.timeout(
154186
subscriptionTimeout,
@@ -169,43 +201,59 @@ class DoorsCubit extends Cubit<DoorsState> with ConsumerBlocMixin {
169201
DataSourceParameterId parameterId = const DataSourceParameterId.leftDoor(),
170202
]) {
171203
_toggleBool(
172-
newFeatureStateBuilder: () => state.left,
173-
newStateBuilder: (newState) => state.copyWith(left: newState),
174-
parameterId: parameterId,
204+
newFeatureStateBuilder: () => state.leftDoor,
205+
newStateBuilder: (newState) => state.copyWith(leftDoor: newState),
206+
outgoingPackageBuilder: (_) =>
207+
OutgoingActionRequestPackage(parameterId: parameterId),
175208
);
176209
}
177210

178211
void toggleRightDoor([
179212
DataSourceParameterId parameterId = const DataSourceParameterId.rightDoor(),
180213
]) {
181214
_toggleBool(
182-
newFeatureStateBuilder: () => state.right,
183-
newStateBuilder: (newState) => state.copyWith(right: newState),
184-
parameterId: parameterId,
215+
newFeatureStateBuilder: () => state.rightDoor,
216+
newStateBuilder: (newState) => state.copyWith(rightDoor: newState),
217+
outgoingPackageBuilder: (_) =>
218+
OutgoingActionRequestPackage(parameterId: parameterId),
219+
);
220+
}
221+
222+
void toggleWindscreenWipers([
223+
DataSourceParameterId parameterId =
224+
const DataSourceParameterId.windscreenWipers(),
225+
]) {
226+
_toggleBool(
227+
newFeatureStateBuilder: () => state.wipers,
228+
newStateBuilder: (newState) => state.copyWith(wipers: newState),
229+
outgoingPackageBuilder: (setTo) => OutgoingSetValuePackage(
230+
parameterId: parameterId,
231+
setValueBody: SetUint8Body(value: setTo ? 0xFF : 0),
232+
),
185233
);
186234
}
187235

188236
Future<void> _toggleBool({
189237
required AsyncData<bool, ToggleStateError> Function()
190238
newFeatureStateBuilder,
191-
required DoorsState Function(
239+
required GeneralInterfacesState Function(
192240
AsyncData<bool, ToggleStateError> newState,
193241
) newStateBuilder,
194-
required DataSourceParameterId parameterId,
242+
// ignore: avoid_positional_boolean_parameters
243+
required DataSourceOutgoingPackage Function(bool setTo)
244+
outgoingPackageBuilder,
195245
}) async {
196246
final currentState = newFeatureStateBuilder();
197247
if (!currentState.isExecuted) return;
198248

199249
final setTo = !currentState.payload;
200250
emit(newStateBuilder(AsyncData.loading(setTo)));
201251

202-
await dataSource.sendPackage(
203-
OutgoingActionRequestPackage(parameterId: parameterId),
204-
);
252+
await dataSource.sendPackage(outgoingPackageBuilder(setTo));
205253

206254
final failure = await stream
207255
.where((event) => newFeatureStateBuilder().payload == setTo)
208-
.map<DoorState?>((event) => null)
256+
.map<GeneralInterfaceState?>((event) => null)
209257
.first
210258
.timeout(
211259
toggleTimeout,

0 commit comments

Comments
 (0)