From c90a5beb9babefdefd5fa66118fb892cfff9324e Mon Sep 17 00:00:00 2001 From: DevCore <62793354+rawi-coding@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:43:35 +0100 Subject: [PATCH] feat: base structure of train journey table (#79) --- das_client/analysis_options.yaml | 3 + das_client/integration_test/app_test.dart | 2 + .../auth/mqtt_client_user_connector.dart | 2 +- .../test/navigation_test.dart | 2 +- .../test/train_journey_table_test.dart | 69 ++++++ .../test/train_journey_test.dart | 2 +- .../test/train_search_test.dart | 18 +- .../integration_test/util/test_utils.dart | 2 +- das_client/l10n/strings_de.arb | 6 + .../lib/app/bloc/train_journey_cubit.dart | 18 +- .../lib/app/bloc/train_journey_state.dart | 2 +- .../lib/app/nav/das_navigation_drawer.dart | 2 +- .../train_journey/train_journey_overview.dart | 3 +- .../widgets/header/adl_notification.dart | 11 +- .../widgets/header/main_container.dart | 39 ++-- .../widgets/table/base_row_builder.dart | 102 +++++++++ .../widgets/table/cells/route_cell_body.dart | 99 ++++++++ .../widgets/table/service_point_row.dart | 42 ++++ .../train_journey/widgets/train_journey.dart | 86 ++++--- .../train_selection/train_selection.dart | 20 +- .../lib/app/pages/login/login_page.dart | 2 +- .../lib/app/widgets/table/das_table.dart | 213 ++++++++++++++++++ .../lib/app/widgets/table/das_table_cell.dart | 32 +++ .../app/widgets/table/das_table_column.dart | 46 ++++ .../lib/app/widgets/table/das_table_row.dart | 25 ++ .../app/widgets/table/das_table_theme.dart | 115 ++++++++++ das_client/lib/logging/src/log_service.dart | 12 +- .../mqtt/src/mqtt_client_oauth_connector.dart | 10 +- .../src/mqtt_client_tms_oauth_connector.dart | 4 +- das_client/lib/service/backend_service.dart | 6 +- .../sfera/src/model/sfera_xml_element.dart | 2 +- .../sfera/src/repo/sfera_repository_impl.dart | 8 +- .../journey_profile_reply_handler.dart | 2 +- .../segment_profile_reply_handler.dart | 2 +- .../sfera/src/service/sfera_auth_service.dart | 6 +- .../sfera/src/service/sfera_service_impl.dart | 16 +- .../src/service/task/handshake_task.dart | 8 +- .../task/request_journey_profile_task.dart | 10 +- .../task/request_segment_profiles_task.dart | 18 +- .../lib/sfera/src/sfera_reply_parser.dart | 16 +- .../test/logging/logging_service_test.dart | 18 +- .../test/sfera/model/sfera_document_test.dart | 26 +-- .../service/sfera_handshake_task_test.dart | 20 +- ...era_request_journey_profile_task_test.dart | 32 +-- ...era_request_segment_profile_task_test.dart | 30 +-- 45 files changed, 996 insertions(+), 213 deletions(-) create mode 100644 das_client/integration_test/test/train_journey_table_test.dart create mode 100644 das_client/lib/app/pages/journey/train_journey/widgets/table/base_row_builder.dart create mode 100644 das_client/lib/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart create mode 100644 das_client/lib/app/pages/journey/train_journey/widgets/table/service_point_row.dart create mode 100644 das_client/lib/app/widgets/table/das_table.dart create mode 100644 das_client/lib/app/widgets/table/das_table_cell.dart create mode 100644 das_client/lib/app/widgets/table/das_table_column.dart create mode 100644 das_client/lib/app/widgets/table/das_table_row.dart create mode 100644 das_client/lib/app/widgets/table/das_table_theme.dart diff --git a/das_client/analysis_options.yaml b/das_client/analysis_options.yaml index 34428d46..50970900 100644 --- a/das_client/analysis_options.yaml +++ b/das_client/analysis_options.yaml @@ -24,6 +24,9 @@ linter: rules: always_use_package_imports: true prefer_single_quotes: true + prefer_final_in_for_each: true + prefer_final_locals: true + prefer_final_fields: true # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/das_client/integration_test/app_test.dart b/das_client/integration_test/app_test.dart index e1b99603..eb9c831b 100644 --- a/das_client/integration_test/app_test.dart +++ b/das_client/integration_test/app_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'di.dart'; +import 'test/train_journey_table_test.dart' as train_journey_table_tests; import 'test/navigation_test.dart' as navigation_tests; import 'test/train_journey_test.dart' as train_journey_tests; import 'test/train_search_test.dart' as train_search_tests; @@ -18,6 +19,7 @@ void main() { Fimber.plantTree(DebugTree()); train_journey_tests.main(); + train_journey_table_tests.main(); navigation_tests.main(); train_search_tests.main(); } diff --git a/das_client/integration_test/auth/mqtt_client_user_connector.dart b/das_client/integration_test/auth/mqtt_client_user_connector.dart index 918937e7..f0237272 100644 --- a/das_client/integration_test/auth/mqtt_client_user_connector.dart +++ b/das_client/integration_test/auth/mqtt_client_user_connector.dart @@ -16,7 +16,7 @@ class MqttClientUserConnector implements MqttClientConnector { } try { - var mqttClientConnectionStatus = + final mqttClientConnectionStatus = await client.connect(const String.fromEnvironment(mqttUsername), const String.fromEnvironment(mqttPassword)); Fimber.i('mqttClientConnectionStatus=$mqttClientConnectionStatus'); diff --git a/das_client/integration_test/test/navigation_test.dart b/das_client/integration_test/test/navigation_test.dart index 37a69ef9..495d4714 100644 --- a/das_client/integration_test/test/navigation_test.dart +++ b/das_client/integration_test/test/navigation_test.dart @@ -15,7 +15,7 @@ void main() { await prepareAndStartApp(tester); // check that there is a drawer - var scaffold = find.byWidgetPredicate((widget) => widget is Scaffold).first; + final scaffold = find.byWidgetPredicate((widget) => widget is Scaffold).first; expect(tester.widget(scaffold).drawer, isNotNull); // check that drawer is not shown diff --git a/das_client/integration_test/test/train_journey_table_test.dart b/das_client/integration_test/test/train_journey_table_test.dart new file mode 100644 index 00000000..e9fdc6ae --- /dev/null +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -0,0 +1,69 @@ +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../app_test.dart'; +import '../util/test_utils.dart'; + +void main() { + group('train journey table test', () { + testWidgets('check if all table columns with header are present', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: '4816'); + + // List of expected column headers + final List expectedHeaders = [ + l10n.p_train_journey_table_kilometre_label, + l10n.p_train_journey_table_journey_information_label, + l10n.p_train_journey_table_time_label, + l10n.p_train_journey_table_advised_speed_label, + l10n.p_train_journey_table_braked_weight_speed_label, + l10n.p_train_journey_table_graduated_speed_label, + ]; + + // Check if each header is present in the widget tree + for (final header in expectedHeaders) { + expect(find.text(header), findsOneWidget); + } + }); + testWidgets('test scrolling to last train station', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: '4816'); + + final scrollableFinder = find.byType(ListView); + expect(scrollableFinder, findsOneWidget); + + // check first train station + expect(find.text('ZUE'), findsOneWidget); + + // Scroll to last train station + await tester.dragUntilVisible( + find.text('AAR'), + find.byType(ListView), + const Offset(0, -300) + ); + }); + }); +} + +/// Verifies, that SBB is selected and loads train journey with [trainNumber] +Future _loadTrainJourney(WidgetTester tester, {required String trainNumber}) async { + // verify we have ru SBB selected. + expect(find.text(l10n.c_ru_sbb_p), findsOneWidget); + + final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); + expect(trainNumberText, findsOneWidget); + + await enterText(tester, trainNumberText, trainNumber); + + // load train journey + final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + await tester.tap(primaryButton); + + // wait for train journey to load + await tester.pumpAndSettle(); +} diff --git a/das_client/integration_test/test/train_journey_test.dart b/das_client/integration_test/test/train_journey_test.dart index 0ff8f2ce..ba5a87b8 100644 --- a/das_client/integration_test/test/train_journey_test.dart +++ b/das_client/integration_test/test/train_journey_test.dart @@ -16,7 +16,7 @@ void main() { expect(find.text(l10n.c_ru_sbb_p), findsOneWidget); // check that the primary button is enabled - var primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; expect(tester.widget(primaryButton).onPressed, isNotNull); // press load Fahrordnung button diff --git a/das_client/integration_test/test/train_search_test.dart b/das_client/integration_test/test/train_search_test.dart index f5ef2003..427ce743 100644 --- a/das_client/integration_test/test/train_search_test.dart +++ b/das_client/integration_test/test/train_search_test.dart @@ -51,13 +51,13 @@ void main() { // Verify that today is preselected expect(find.text(Format.date(DateTime.now())), findsOneWidget); - var trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); + final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); expect(trainNumberText, findsOneWidget); await enterText(tester, trainNumberText, ''); // check that the primary button is disabled - var primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; expect(tester.widget(primaryButton).onPressed, isNull); }); @@ -69,8 +69,8 @@ void main() { final today = DateTime.now(); final yesterday = today.add(Duration(days: -1)); - var todayDateTextFinder = find.text(Format.date(today)); - var yesterdayDateTextFinder = find.text(Format.date(yesterday)); + final todayDateTextFinder = find.text(Format.date(today)); + final yesterdayDateTextFinder = find.text(Format.date(yesterday)); // Verify that today is preselected expect(todayDateTextFinder, findsOneWidget); @@ -101,9 +101,9 @@ void main() { final yesterday = today.add(Duration(days: -1)); final dayBeforeYesterday = today.add(Duration(days: -2)); - var todayDateTextFinder = find.text(Format.date(today)); - var yesterdayDateTextFinder = find.text(Format.date(yesterday)); - var dayBeforeYesterdayDateTextFinder = find.text(Format.date(dayBeforeYesterday)); + final todayDateTextFinder = find.text(Format.date(today)); + final yesterdayDateTextFinder = find.text(Format.date(yesterday)); + final dayBeforeYesterdayDateTextFinder = find.text(Format.date(dayBeforeYesterday)); // Verify that today is preselected expect(todayDateTextFinder, findsOneWidget); @@ -136,13 +136,13 @@ void main() { // Verify that today is preselected expect(find.text(Format.date(DateTime.now())), findsOneWidget); - var trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); + final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); expect(trainNumberText, findsOneWidget); await enterText(tester, trainNumberText, '1234'); // check that the primary button is disabled - var primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; expect(tester.widget(primaryButton).onPressed, isNotNull); await tapElement(tester, primaryButton); diff --git a/das_client/integration_test/util/test_utils.dart b/das_client/integration_test/util/test_utils.dart index a4b0ae19..7bffcf25 100644 --- a/das_client/integration_test/util/test_utils.dart +++ b/das_client/integration_test/util/test_utils.dart @@ -21,6 +21,6 @@ Future enterText(WidgetTester tester, FinderBase element, String } Finder findTextFieldByLabel(String label) { - var sbbTextField = find.byWidgetPredicate((widget) => widget is SBBTextField && widget.labelText == label); + final sbbTextField = find.byWidgetPredicate((widget) => widget is SBBTextField && widget.labelText == label); return find.descendant(of: sbbTextField, matching: find.byType(TextField)); } diff --git a/das_client/l10n/strings_de.arb b/das_client/l10n/strings_de.arb index 71be7766..a1fc30cf 100644 --- a/das_client/l10n/strings_de.arb +++ b/das_client/l10n/strings_de.arb @@ -7,6 +7,12 @@ "p_train_selection_choose_date": "Datum wählen", "p_train_journey_header_button_dark_theme": "Nachtmodus", "p_train_journey_header_button_pause": "Pause", + "p_train_journey_table_kilometre_label": "km", + "p_train_journey_table_time_label": "an/ab", + "p_train_journey_table_journey_information_label": "Streckeninformationen", + "p_train_journey_table_advised_speed_label": "FE", + "p_train_journey_table_graduated_speed_label": "OG", + "p_train_journey_table_braked_weight_speed_label": "R150", "w_navigation_drawer_fahrtinfo_title": "Fahrtinfo", "w_navigation_drawer_links_title": "Links", "w_navigation_drawer_settings_title": "Einstellungen", diff --git a/das_client/lib/app/bloc/train_journey_cubit.dart b/das_client/lib/app/bloc/train_journey_cubit.dart index 7d3526aa..c7dc8265 100644 --- a/das_client/lib/app/bloc/train_journey_cubit.dart +++ b/das_client/lib/app/bloc/train_journey_cubit.dart @@ -27,34 +27,34 @@ class TrainJourneyCubit extends Cubit { final currentState = state; if (currentState is SelectingTrainJourneyState) { final date = currentState.date; - final evu = currentState.ru; + final ru = currentState.ru; final trainNumber = currentState.trainNumber; - if (evu == null || trainNumber == null) { + if (ru == null || trainNumber == null) { Fimber.i('company or trainNumber null'); return; } - emit(ConnectingState(evu, trainNumber, currentState.date)); + emit(ConnectingState(ru, trainNumber, currentState.date)); _stateSubscription?.cancel(); _stateSubscription = _sferaService.stateStream.listen((state) { switch (state) { case SferaServiceState.connected: - emit(TrainJourneyLoadedState(evu, trainNumber, date)); + emit(TrainJourneyLoadedState(ru, trainNumber, date)); break; case SferaServiceState.connecting: case SferaServiceState.handshaking: case SferaServiceState.loadingJourney: case SferaServiceState.loadingSegments: - emit(ConnectingState(evu, trainNumber, date)); + emit(ConnectingState(ru, trainNumber, date)); break; case SferaServiceState.disconnected: case SferaServiceState.offline: emit(SelectingTrainJourneyState( - ru: evu, trainNumber: trainNumber, date: date, errorCode: _sferaService.lastErrorCode)); + ru: ru, trainNumber: trainNumber, date: date, errorCode: _sferaService.lastErrorCode)); break; } }); - _sferaService.connect(OtnId.create(evu.companyCode, trainNumber, date)); + _sferaService.connect(OtnId.create(ru.companyCode, trainNumber, date)); } } @@ -68,11 +68,11 @@ class TrainJourneyCubit extends Cubit { } } - void updateCompany(Ru? evu) { + void updateCompany(Ru? ru) { if (state is SelectingTrainJourneyState) { emit(SelectingTrainJourneyState( trainNumber: (state as SelectingTrainJourneyState).trainNumber, - ru: evu, + ru: ru, date: (state as SelectingTrainJourneyState).date, errorCode: (state as SelectingTrainJourneyState).errorCode)); } diff --git a/das_client/lib/app/bloc/train_journey_state.dart b/das_client/lib/app/bloc/train_journey_state.dart index f8abaf01..818b68c0 100644 --- a/das_client/lib/app/bloc/train_journey_state.dart +++ b/das_client/lib/app/bloc/train_journey_state.dart @@ -21,7 +21,7 @@ abstract class BaseTrainJourneyState extends TrainJourneyState { @override String toString() { - return '${runtimeType.toString()}(evu=$ru, trainNumber=$trainNumber, date=$date)'; + return '${runtimeType.toString()}(ru=$ru, trainNumber=$trainNumber, date=$date)'; } } diff --git a/das_client/lib/app/nav/das_navigation_drawer.dart b/das_client/lib/app/nav/das_navigation_drawer.dart index dbdc3832..aee710c8 100644 --- a/das_client/lib/app/nav/das_navigation_drawer.dart +++ b/das_client/lib/app/nav/das_navigation_drawer.dart @@ -51,7 +51,7 @@ class DASNavigationDrawer extends StatelessWidget { Widget _navigationTile(BuildContext context, {required IconData icon, required String title, required PageRouteInfo route}) { - bool isActiveRoute = context.router.isRouteActive(route.routeName); + final bool isActiveRoute = context.router.isRouteActive(route.routeName); return ListTile( leading: isActiveRoute ? _activeIcon(icon) : _inactiveIcon(icon), diff --git a/das_client/lib/app/pages/journey/train_journey/train_journey_overview.dart b/das_client/lib/app/pages/journey/train_journey/train_journey_overview.dart index 9427d507..2ec6d6bb 100644 --- a/das_client/lib/app/pages/journey/train_journey/train_journey_overview.dart +++ b/das_client/lib/app/pages/journey/train_journey/train_journey_overview.dart @@ -1,6 +1,7 @@ import 'package:das_client/app/pages/journey/train_journey/widgets/header/adl_notification.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/header/header.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/train_journey.dart'; +import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; // TODO: handle extraLarge font sizes (diff to figma) globally. @@ -15,7 +16,7 @@ class TrainJourneyOverview extends StatelessWidget { Header(), ADLNotification( message: 'VMax fahren bis Wettingen', - margin: EdgeInsets.fromLTRB(8, 0, 8, 16), + margin: EdgeInsets.fromLTRB(sbbDefaultSpacing * 0.5, 0, sbbDefaultSpacing * 0.5, sbbDefaultSpacing), ), Expanded(child: TrainJourney()), ], diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/header/adl_notification.dart b/das_client/lib/app/pages/journey/train_journey/widgets/header/adl_notification.dart index 1eaf9ce9..9fee1aeb 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/header/adl_notification.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/header/adl_notification.dart @@ -10,21 +10,22 @@ class ADLNotification extends StatelessWidget { @override Widget build(BuildContext context) { + final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; + final fontColor = isDarkTheme ? SBBColors.charcoal : SBBColors.white; return Container( margin: margin, decoration: BoxDecoration( - color: SBBColors.charcoal, + color: isDarkTheme ? SBBColors.cloud : SBBColors.charcoal, borderRadius: BorderRadius.circular(sbbDefaultSpacing), ), - padding: const EdgeInsets.symmetric(vertical: 14.0) - .copyWith(left: sbbDefaultSpacing, right: 4.0), + padding: const EdgeInsets.symmetric(vertical: 14.0).copyWith(left: sbbDefaultSpacing, right: 4.0), child: Row( children: [ - const Icon(SBBIcons.circle_information_small, color: SBBColors.white), + Icon(SBBIcons.circle_information_small, color: fontColor), const SizedBox(width: sbbDefaultSpacing * 0.5), Text( '${context.l10n.w_adl_notification_title}: $message', - style: SBBTextStyles.mediumBold.copyWith(color: SBBColors.white), + style: SBBTextStyles.mediumBold.copyWith(color: fontColor), ), ], ), diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart b/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart index be32cf53..01bf96c8 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart @@ -1,3 +1,4 @@ +import 'package:das_client/app/i18n/i18n.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/header/departure_authorization.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/header/radio_channel.dart'; import 'package:das_client/app/widgets/widget_extensions.dart'; @@ -70,23 +71,27 @@ class MainContainer extends StatelessWidget { } Widget _buttonArea() { - return Row( - children: [ - SBBTertiaryButtonLarge( - label: 'Nachtmodus', - icon: SBBIcons.moon_small, - onPressed: () {}, - ), - SBBTertiaryButtonLarge( - label: 'Pause', - icon: SBBIcons.pause_small, - onPressed: () {}, - ), - SBBIconButtonLarge( - icon: SBBIcons.context_menu_small, - onPressed: () {}, - ), - ].withSpacing(width: sbbDefaultSpacing * 0.5), + return Builder( + builder: (context) { + return Row( + children: [ + SBBTertiaryButtonLarge( + label: context.l10n.p_train_journey_header_button_dark_theme, + icon: SBBIcons.moon_small, + onPressed: () {}, + ), + SBBTertiaryButtonLarge( + label: context.l10n.p_train_journey_header_button_pause, + icon: SBBIcons.pause_small, + onPressed: () {}, + ), + SBBIconButtonLarge( + icon: SBBIcons.context_menu_small, + onPressed: () {}, + ), + ].withSpacing(width: sbbDefaultSpacing * 0.5), + ); + } ); } } diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/base_row_builder.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/base_row_builder.dart new file mode 100644 index 00000000..0af81b82 --- /dev/null +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/base_row_builder.dart @@ -0,0 +1,102 @@ +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart'; +import 'package:das_client/app/widgets/table/das_table_cell.dart'; +import 'package:das_client/app/widgets/table/das_table_row.dart'; +import 'package:flutter/material.dart'; + +abstract class BaseRowBuilder extends DASTableRowBuilder { + const BaseRowBuilder({ + super.height, + this.kilometre, + this.defaultAlignment = Alignment.centerLeft, + this.rowColor, + this.isCurrentPosition = false, + this.isServicePointStop = false, + }); + + final double? kilometre; + final Alignment defaultAlignment; + final Color? rowColor; + final bool isServicePointStop; + final bool isCurrentPosition; + + @override + DASTableRow build(BuildContext context) { + return DASTableRow( + height: height, + color: rowColor, + cells: [ + kilometreCell(), + timeCell(), + routeCell(), + iconsCell1(), + informationCell(), + iconsCell2(), + iconsCell3(), + graduatedSpeedCell(), + brakedWeightSpeedCell(), + advisedSpeedCell(), + actionsCell(), + ], + ); + } + + DASTableCell kilometreCell() { + if (kilometre == null) { + return DASTableCell.empty(); + } + + var kilometreAsString = kilometre!.toStringAsFixed(3); + kilometreAsString = kilometreAsString.replaceAll(RegExp(r'0*$'), ''); + return DASTableCell(child: Text(kilometreAsString), alignment: defaultAlignment); + } + + DASTableCell timeCell() { + return DASTableCell(child: Text('06:05:52'), alignment: defaultAlignment); + } + + DASTableCell routeCell() { + return DASTableCell( + padding: EdgeInsets.all(0.0), + alignment: null, + child: RouteCellBody( + showCircle: isServicePointStop, + showChevron: isCurrentPosition, + ), + ); + } + + DASTableCell informationCell() { + return DASTableCell.empty(); + } + + DASTableCell graduatedSpeedCell() { + return DASTableCell(child: Text('85'), alignment: defaultAlignment); + } + + DASTableCell advisedSpeedCell() { + return DASTableCell(child: Text('100'), alignment: defaultAlignment); + } + + DASTableCell brakedWeightSpeedCell() { + return DASTableCell(child: Text('95'), alignment: defaultAlignment); + } + + // TODO: clarify use of different icon cells and set appropriate name + DASTableCell iconsCell1() { + return DASTableCell.empty(); + } + + // TODO: clarify use of different icon cells and set appropriate name + DASTableCell iconsCell2() { + return DASTableCell.empty(); + } + + // TODO: clarify use of different icon cells and set appropriate name + DASTableCell iconsCell3() { + return DASTableCell.empty(); + } + + DASTableCell actionsCell() { + return DASTableCell.empty(); + } +} diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart new file mode 100644 index 00000000..6563cec1 --- /dev/null +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart @@ -0,0 +1,99 @@ +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +class RouteCellBody extends StatelessWidget { + const RouteCellBody({ + super.key, + this.chevronHeight = 8.0, + this.chevronWidth = 16.0, + this.circleSize = 14.0, + this.lineThickness = 2.0, + this.showCircle = false, + this.showChevron = false, + }); + + final double chevronHeight; + final double chevronWidth; + final double circleSize; + final double lineThickness; + + final bool showChevron; + final bool showCircle; + + @override + Widget build(BuildContext context) { + return Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + if (showChevron) _chevron(context), + if (showCircle) _circle(context), + _routeLine(context), + ], + ); + } + + Positioned _routeLine(BuildContext context) { + final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; + final lineColor = isDarkTheme ? SBBColors.white : SBBColors.black; + return Positioned( + top: -sbbDefaultSpacing, + bottom: -sbbDefaultSpacing, + right: 0, + left: 0, + child: VerticalDivider(thickness: lineThickness, color: lineColor), + ); + } + + Positioned _circle(BuildContext context) { + final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; + final circleColor = isDarkTheme ? SBBColors.sky : SBBColors.black; + return Positioned( + bottom: sbbDefaultSpacing, + child: Container( + width: circleSize, + height: circleSize, + decoration: BoxDecoration( + color: circleColor, + shape: BoxShape.circle, + ), + ), + ); + } + + Positioned _chevron(BuildContext context) { + final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; + final chevronColor = isDarkTheme ? SBBColors.sky : SBBColors.black; + return Positioned( + bottom: showCircle ? sbbDefaultSpacing + circleSize : sbbDefaultSpacing, + child: CustomPaint( + size: Size(chevronWidth, chevronHeight), + painter: _ChevronPainter(color: chevronColor), + ), + ); + } +} + +class _ChevronPainter extends CustomPainter { + _ChevronPainter({this.color = SBBColors.black}); + + final Color color; + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = 4; + + final path = Path() + ..moveTo(0, 0) + ..lineTo(size.width / 2, size.height) + ..lineTo(size.width, 0); + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/service_point_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/service_point_row.dart new file mode 100644 index 00000000..79f3113e --- /dev/null +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/service_point_row.dart @@ -0,0 +1,42 @@ +import 'package:das_client/app/pages/journey/train_journey/widgets/table/base_row_builder.dart'; +import 'package:das_client/app/widgets/table/das_table_cell.dart'; +import 'package:das_client/sfera/sfera_component.dart'; +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +// TODO: Extract real values from SFERA objects. +class ServicePointRow extends BaseRowBuilder { + ServicePointRow({ + super.height = 64.0, + super.defaultAlignment = _defaultAlignment, + super.kilometre, + super.isCurrentPosition, + super.isServicePointStop, + this.timingPoint, + this.timingPointConstraints, + bool nextStop = false, + }) : super( + rowColor: nextStop ? SBBColors.royal.withOpacity(0.2) : Colors.transparent, + ); + + final TimingPoint? timingPoint; + final TimingPointConstraints? timingPointConstraints; + + static const Alignment _defaultAlignment = Alignment.bottomCenter; + + @override + DASTableCell informationCell() { + final servicePointName = timingPoint?.names.first.name ?? 'Unknown'; + return DASTableCell( + alignment: _defaultAlignment, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text(servicePointName, style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0)), + Spacer(), + Text('B12'), + ], + ), + ); + } +} diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/train_journey.dart b/das_client/lib/app/pages/journey/train_journey/widgets/train_journey.dart index 37daff19..bbe7c764 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/train_journey.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/train_journey.dart @@ -1,4 +1,8 @@ import 'package:das_client/app/bloc/train_journey_cubit.dart'; +import 'package:das_client/app/i18n/i18n.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/service_point_row.dart'; +import 'package:das_client/app/widgets/table/das_table.dart'; +import 'package:das_client/app/widgets/table/das_table_column.dart'; import 'package:das_client/sfera/sfera_component.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; @@ -12,19 +16,21 @@ class TrainJourney extends StatelessWidget { final bloc = context.trainJourneyCubit; return StreamBuilder>( - stream: CombineLatestStream.list([bloc.journeyStream, bloc.segmentStream]), - builder: (context, snapshot) { - JourneyProfile? journeyProfile = snapshot.data?[0]; - List segmentProfiles = snapshot.data?[1] ?? []; - if (journeyProfile == null) { - return Container(); - } + stream: CombineLatestStream.list([bloc.journeyStream, bloc.segmentStream]), + builder: (context, snapshot) { + final JourneyProfile? journeyProfile = snapshot.data?[0]; + final List segmentProfiles = snapshot.data?[1] ?? []; + if (journeyProfile == null) { + return Container(); + } - return _body(journeyProfile, segmentProfiles); - }); + return _body(context, journeyProfile, segmentProfiles); + }, + ); } Widget _body( + BuildContext context, JourneyProfile journeyProfile, List segmentProfiles, ) { @@ -34,32 +40,54 @@ class TrainJourney extends StatelessWidget { final points = segmentProfiles.expand((it) => it.points?.timingPoints.toList() ?? []); - return SingleChildScrollView( - child: Column( - children: [ + return Padding( + padding: const EdgeInsets.symmetric(horizontal: sbbDefaultSpacing * 0.5), + child: DASTable( + columns: _columns(context), + rows: [ ...List.generate(timingPoints.length, (index) { - var timingPoint = timingPoints[index]; - var tpId = timingPoint.timingPointReference.children.whereType().firstOrNull?.tpId; - var tp = points.where((point) => point.id == tpId).firstOrNull; - return Padding( - padding: const EdgeInsets.all(sbbDefaultSpacing * 0.5), - child: Row( - children: [ - _arrivalTime(timingPoint), - const SizedBox(width: sbbDefaultSpacing), - _servicePointName(tp), - ], - ), - ); + final timingPoint = timingPoints[index]; + final tpId = timingPoint.timingPointReference.children.whereType().firstOrNull?.tpId; + final tp = points.where((point) => point.id == tpId).firstOrNull; + + return ServicePointRow( + kilometre: 10.2, + timingPoint: tp, + timingPointConstraints: timingPoint, + nextStop: index == 1, + isServicePointStop: index != 3 && index != 4, + isCurrentPosition: index == 0 || index == 3, + ).build(context); }) ], ), ); } - Widget _servicePointName(TimingPoint? tp) => Text(tp?.names.first.name ?? 'Unknown'); - - Widget _arrivalTime(TimingPointConstraints timingPoint) { - return Text(timingPoint.attributes['TP_PlannedLatestArrivalTime'] ?? ''); + List _columns(BuildContext context) { + return [ + DASTableColumn(child: Text(context.l10n.p_train_journey_table_kilometre_label), width: 64.0), + DASTableColumn(child: Text(context.l10n.p_train_journey_table_time_label), width: 100.0), + DASTableColumn(width: 48.0), // route column + DASTableColumn(width: 64.0), // icons column + DASTableColumn( + child: Text(context.l10n.p_train_journey_table_journey_information_label), + expanded: true, + alignment: Alignment.centerLeft, + ), + DASTableColumn(width: 68.0), // icons column + DASTableColumn(width: 48.0), // icons column + DASTableColumn( + child: Text(context.l10n.p_train_journey_table_graduated_speed_label), + width: 100.0, + border: BorderDirectional( + bottom: BorderSide(color: SBBColors.cloud, width: 1.0), + end: BorderSide(color: SBBColors.cloud, width: 2.0), + ), + ), + DASTableColumn(child: Text(context.l10n.p_train_journey_table_braked_weight_speed_label), width: 62.0), + DASTableColumn(child: Text(context.l10n.p_train_journey_table_advised_speed_label), width: 62.0), + DASTableColumn(width: 40.0), // actions + ]; } } diff --git a/das_client/lib/app/pages/journey/train_selection/train_selection.dart b/das_client/lib/app/pages/journey/train_selection/train_selection.dart index f7b3a2e2..49f44152 100644 --- a/das_client/lib/app/pages/journey/train_selection/train_selection.dart +++ b/das_client/lib/app/pages/journey/train_selection/train_selection.dart @@ -112,14 +112,7 @@ class _TrainSelectionState extends State { padding: const EdgeInsets.symmetric(vertical: sbbDefaultSpacing, horizontal: sbbDefaultSpacing / 2), child: SBBPrimaryButton( label: context.l10n.p_train_selection_load, - onPressed: _canContinue(state) - ? () { - final trainJourneyCubit = context.trainJourneyCubit; - if (!trainJourneyCubit.isClosed) { - trainJourneyCubit.loadTrainJourney(); - } - } - : null, + onPressed: _canContinue(state) ? () => context.trainJourneyCubit.loadTrainJourney() : null, ), ); } @@ -130,16 +123,17 @@ class _TrainSelectionState extends State { } Widget _datePickerWidget(BuildContext context, SelectingTrainJourneyState state) { - DateTime now = DateTime.now(); + final now = DateTime.now(); return Column( mainAxisSize: MainAxisSize.min, children: [ SBBDatePicker( - initialDate: state.date, - minimumDate: now.add(Duration(days: -1)), - maximumDate: now.add(Duration(hours: 4)), - onDateChanged: (value) => context.trainJourneyCubit.updateDate(value)), + initialDate: state.date, + minimumDate: now.add(Duration(days: -1)), + maximumDate: now.add(Duration(hours: 4)), + onDateChanged: (value) => context.trainJourneyCubit.updateDate(value), + ), ], ); } diff --git a/das_client/lib/app/pages/login/login_page.dart b/das_client/lib/app/pages/login/login_page.dart index 87de7aca..e9ab8012 100644 --- a/das_client/lib/app/pages/login/login_page.dart +++ b/das_client/lib/app/pages/login/login_page.dart @@ -86,7 +86,7 @@ class _LoginPageState extends State { } Widget _tmsCheckbox(BuildContext context) { - var flavor = DI.get(); + final flavor = DI.get(); if (flavor.tmsAuthenticatorConfig != null && flavor.tmsMqttUrl != null && flavor.tmsTokenExchangeUrl != null) { return Padding( diff --git a/das_client/lib/app/widgets/table/das_table.dart b/das_client/lib/app/widgets/table/das_table.dart new file mode 100644 index 00000000..ee9ef72a --- /dev/null +++ b/das_client/lib/app/widgets/table/das_table.dart @@ -0,0 +1,213 @@ +import 'package:collection/collection.dart'; +import 'package:das_client/app/widgets/table/das_table_cell.dart'; +import 'package:das_client/app/widgets/table/das_table_column.dart'; +import 'package:das_client/app/widgets/table/das_table_row.dart'; +import 'package:das_client/app/widgets/table/das_table_theme.dart'; +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +/// [DASTable] provides a the basic structure for a train journey table. +/// +/// The [columns] parameter must not be empty, and all rows must have the same number of cells as columns. +@immutable +class DASTable extends StatelessWidget { + DASTable({ + super.key, + required this.columns, + this.rows = const [], + this.scrollController, + this.bottomMargin = 32.0, + this.themeData, + }) : assert(columns.isNotEmpty), + assert(!rows.any((DASTableRow row) => row.cells.length != columns.length), + 'All rows must have the same number of cells as there are header cells (${columns.length})'); + + /// List of rows to be displayed in the table. + final List rows; + + /// List of columns defining the structure and heading of the table. + final List columns; + + /// Theme data used to style the table. + final DASTableThemeData? themeData; + + /// Scroll controller for managing scrollable content (rows) within the table. + final ScrollController? scrollController; + + /// The bottom margin to be applied at the end of the scrollable content of the table. + final double bottomMargin; + + @override + Widget build(BuildContext context) { + final tableThemeData = themeData ?? _defaultThemeData(context); + return DASTableTheme( + data: tableThemeData, + child: Container( + decoration: BoxDecoration( + color: tableThemeData.backgroundColor, + borderRadius: tableThemeData.tableBorder?.borderRadius, + border: BorderDirectional( + top: tableThemeData.tableBorder?.top ?? BorderSide.none, + start: tableThemeData.tableBorder?.left ?? BorderSide.none, + end: tableThemeData.tableBorder?.right ?? BorderSide.none, + bottom: tableThemeData.tableBorder?.bottom ?? BorderSide.none, + ), + ), + child: Column( + children: [ + _headerRow(), + Expanded( + child: ListView.builder( + controller: scrollController, + itemCount: rows.length + 1, // + 1 for bottom spacer + itemBuilder: (context, index) { + if (index == rows.length) { + return SizedBox(height: bottomMargin); + } + return _dataRow(rows[index]); + }, + ), + ), + ], + ), + ), + ); + } + + DASTableThemeData _defaultThemeData(BuildContext context) { + final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; + final borderColor = isDarkTheme ? SBBColors.iron : SBBColors.cloud; + return DASTableThemeData( + backgroundColor: isDarkTheme ? SBBColors.charcoal : SBBColors.white, + headingTextStyle: SBBTextStyles.smallLight, + dataTextStyle: SBBTextStyles.largeLight, + headingRowBorder: Border(bottom: BorderSide(width: 2, color: borderColor)), + tableBorder: TableBorder( + horizontalInside: BorderSide(width: 1, color: borderColor), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(sbbDefaultSpacing), + topRight: Radius.circular(sbbDefaultSpacing), + ), + ), + ); + } + + Widget _headerRow() { + return _FlexibleHeightRow( + children: columns.where((column) => column.isVisible).map((column) => _headerCell(column)).toList(), + ); + } + + Widget _headerCell(DASTableColumn column) { + return Builder(builder: (context) { + final tableThemeData = DASTableTheme.of(context)?.data; + return _TableCellWrapper( + expanded: column.expanded, + width: column.width, + child: Container( + decoration: BoxDecoration( + border: tableThemeData?.headingRowBorder ?? column.border, + color: column.color ?? tableThemeData?.headingRowColor, + ), + padding: column.padding, + child: column.child == null + ? SizedBox.shrink() + : DefaultTextStyle( + style: DefaultTextStyle.of(context).style.merge(tableThemeData?.headingTextStyle), + child: column.alignment != null + ? Align(alignment: column.alignment!, child: column.child) + : column.child!, + ), + ), + ); + }); + } + + Widget _dataRow(DASTableRow row) { + final visibleColumns = columns.where((column) => column.isVisible).toList(growable: false); + final visibleCells = row.cells.whereIndexed((index, _) => columns[index].isVisible).toList(growable: false); + return _FlexibleHeightRow( + fixedHeight: row.height, + children: List.generate(visibleColumns.length, (index) { + final cell = visibleCells[index]; + final column = visibleColumns[index]; + return _dataCell(cell, column, row, isLast: visibleColumns.length - 1 == index); + }), + ); + } + + Widget _dataCell(DASTableCell cell, DASTableColumn column, DASTableRow row, {isLast = false}) { + return Builder(builder: (context) { + final tableThemeData = DASTableTheme.of(context)?.data; + final effectiveAlignment = cell.alignment ?? column.alignment; + return _TableCellWrapper( + expanded: column.expanded, + width: column.width, + child: InkWell( + onTap: cell.onTap, + child: Container( + decoration: BoxDecoration( + border: cell.border ?? column.border ?? tableThemeData?.tableBorder?.toBoxBorder(isLastCell: isLast), + color: cell.color ?? row.color ?? column.color ?? tableThemeData?.dataRowColor, + ), + padding: cell.padding ?? column.padding ?? EdgeInsets.all(sbbDefaultSpacing * 0.5), + clipBehavior: cell.clipBehaviour, + child: DefaultTextStyle( + style: DefaultTextStyle.of(context).style.merge(tableThemeData?.dataTextStyle), + child: effectiveAlignment != null ? Align(alignment: effectiveAlignment, child: cell.child) : cell.child, + ), + ), + ), + ); + }); + } +} + +extension _TableBorderExtension on TableBorder { + toBoxBorder({bool isLastCell = false}) { + return BorderDirectional(bottom: horizontalInside, end: isLastCell ? BorderSide.none : verticalInside); + } +} + +/// Row that handles height of its children with optional fixed height. +/// +/// If [fixedHeight] is provided, the row will have that height; otherwise, it will use intrinsic height. +class _FlexibleHeightRow extends StatelessWidget { + const _FlexibleHeightRow({this.fixedHeight, required this.children}); + + final double? fixedHeight; + final List children; + + @override + Widget build(BuildContext context) { + final row = Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: children); + if (fixedHeight != null) { + return SizedBox(height: fixedHeight, child: row); + } + return IntrinsicHeight(child: row); + } +} + +/// A wrapper for table cells that allows for optional width and expansion. +class _TableCellWrapper extends StatelessWidget { + const _TableCellWrapper({this.width, this.expanded = false, required this.child}); + + /// The fixed width for the cell. + final double? width; + + /// If true, the cell is wrapped with a Expanded widget. + final bool expanded; + + final Widget child; + + @override + Widget build(BuildContext context) { + if (expanded) { + return Expanded(child: child); + } else if (width != null) { + return SizedBox(width: width, child: child); + } else { + return child; + } + } +} diff --git a/das_client/lib/app/widgets/table/das_table_cell.dart b/das_client/lib/app/widgets/table/das_table_cell.dart new file mode 100644 index 00000000..99bc78cc --- /dev/null +++ b/das_client/lib/app/widgets/table/das_table_cell.dart @@ -0,0 +1,32 @@ +import 'package:das_client/app/widgets/table/das_table_column.dart'; +import 'package:das_client/app/widgets/table/das_table_row.dart'; +import 'package:das_client/app/widgets/table/das_table_theme.dart'; +import 'package:flutter/material.dart'; + +/// Represents a cell in the [DASTable] with optional styling and behavior. +/// +/// If no styling is provided, it may be provided by [DASTableRow] or [DASTableTheme] +@immutable +class DASTableCell { + const DASTableCell({ + required this.child, + this.onTap, + this.border, + this.color, + this.padding, + this.alignment, + this.clipBehaviour = Clip.hardEdge, + }); + + const DASTableCell.empty() : this(child: const SizedBox.shrink()); + + final BoxBorder? border; + final Widget child; + final VoidCallback? onTap; + final Color? color; + final EdgeInsetsGeometry? padding; + final Clip clipBehaviour; + + /// If provided, wraps child in Align widget. Can also be defined in [DASTableColumn] + final Alignment? alignment; +} diff --git a/das_client/lib/app/widgets/table/das_table_column.dart b/das_client/lib/app/widgets/table/das_table_column.dart new file mode 100644 index 00000000..bf5e9796 --- /dev/null +++ b/das_client/lib/app/widgets/table/das_table_column.dart @@ -0,0 +1,46 @@ +import 'package:das_client/app/widgets/table/das_table_cell.dart'; +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +/// Represents a column in the [DASTable] with optional styling and width constraints. +/// Styles the heading and data cells if not explicitly overridden by the cells +/// +/// Either [width] or [expanded] must be defined. There is no support for variable width. +@immutable +class DASTableColumn { + const DASTableColumn({ + this.child, + this.border, + this.color, + this.padding = const EdgeInsets.all(sbbDefaultSpacing * 0.5), + this.expanded = false, + this.width, + this.hidden = false, + this.alignment = Alignment.center, + }) : assert((width != null && width > 0) || expanded); + + /// The content of the column header as a widget. + final Widget? child; + + /// Border style for the heading and data cells + final BoxBorder? border; + + /// The background color for the heading and data cells + final Color? color; + + final EdgeInsetsGeometry? padding; + + /// Whether the column should expand to fill available space. + final bool expanded; + + /// The fixed width for the column. Must be specified if not expanded. + final double? width; + + /// If provided, wraps child in Align widget. Can be overridden in [DASTableCell] + final Alignment? alignment; + + /// Whether the column is visible or not. + final bool hidden; + + get isVisible => !hidden; +} diff --git a/das_client/lib/app/widgets/table/das_table_row.dart b/das_client/lib/app/widgets/table/das_table_row.dart new file mode 100644 index 00000000..0ebc9a51 --- /dev/null +++ b/das_client/lib/app/widgets/table/das_table_row.dart @@ -0,0 +1,25 @@ +import 'package:das_client/app/widgets/table/das_table_cell.dart'; +import 'package:flutter/material.dart'; + +/// Interface for a class that builds [DASTableRow] +abstract class DASTableRowBuilder { + const DASTableRowBuilder({this.height}); + + DASTableRow build(BuildContext context); + + final double? height; +} + +/// Represents a row in the [DASTable] containing cells. +@immutable +class DASTableRow { + const DASTableRow({required this.cells, this.height, this.color}); + + /// The fixed height for the row. If null, intrinsic height will be used. + final double? height; + + /// The background color for all cells of the row if not overridden by cell style. + final Color? color; + + final List cells; +} diff --git a/das_client/lib/app/widgets/table/das_table_theme.dart b/das_client/lib/app/widgets/table/das_table_theme.dart new file mode 100644 index 00000000..a8060dd2 --- /dev/null +++ b/das_client/lib/app/widgets/table/das_table_theme.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +/// Contains the theme data for styling the [DASTable]. +@immutable +class DASTableThemeData { + const DASTableThemeData({ + this.backgroundColor, + this.dataRowColor, + this.headingRowColor, + this.tableBorder, + this.headingRowBorder, + this.headingTextStyle, + this.dataTextStyle, + }); + + /// The background color of the table. + final Color? backgroundColor; + + /// The background color of data rows. + final Color? dataRowColor; + + /// The background color of the heading row. + final Color? headingRowColor; + + /// The text style for data cells. Will be overridden if Text in cells provide own style. + final TextStyle? dataTextStyle; + + /// The text style for heading cells. Will be overridden if Text in cells provide own style. + final TextStyle? headingTextStyle; + + /// The border style for the heading row. + final Border? headingRowBorder; + + /// The border style for the table. + final TableBorder? tableBorder; + + DASTableThemeData copyWith({ + Color? dataRowColor, + Color? headingRowColor, + TableBorder? tableBorder, + TextStyle? dataTextStyle, + TextStyle? headingTextStyle, + Border? headingRowBorder, + }) { + return DASTableThemeData( + dataRowColor: dataRowColor ?? this.dataRowColor, + headingRowColor: headingRowColor ?? this.headingRowColor, + tableBorder: tableBorder ?? this.tableBorder, + dataTextStyle: dataTextStyle ?? this.dataTextStyle, + headingTextStyle: headingTextStyle ?? this.headingTextStyle, + headingRowBorder: headingRowBorder ?? this.headingRowBorder, + ); + } + + static DASTableThemeData lerp(DASTableThemeData a, DASTableThemeData b, double t) { + if (identical(a, b)) { + return a; + } + return DASTableThemeData( + dataRowColor: Color.lerp(a.dataRowColor, b.dataRowColor, t), + headingRowColor: Color.lerp(a.headingRowColor, b.headingRowColor, t), + tableBorder: TableBorder.lerp(a.tableBorder, b.tableBorder, t), + headingTextStyle: TextStyle.lerp(a.headingTextStyle, b.headingTextStyle, t), + dataTextStyle: TextStyle.lerp(a.dataTextStyle, b.dataTextStyle, t), + headingRowBorder: Border.lerp(a.headingRowBorder, b.headingRowBorder, t), + ); + } + + @override + int get hashCode => Object.hash( + dataRowColor, + headingRowColor, + tableBorder, + dataTextStyle, + headingTextStyle, + headingRowBorder, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is DASTableThemeData && + other.dataRowColor == dataRowColor && + other.headingRowColor == headingRowColor && + other.headingTextStyle == headingTextStyle && + other.dataTextStyle == dataTextStyle && + other.headingRowBorder == headingRowBorder && + other.tableBorder == tableBorder; + } +} + +/// A widget that provides the theme data for the [DASTable] and its descendants. +class DASTableTheme extends InheritedWidget { + const DASTableTheme({ + super.key, + required this.data, + required super.child, + }); + + /// The properties used for all descendant [DASTableTheme] widgets. + final DASTableThemeData data; + + /// Retrieves the nearest [DASTableTheme] instance from the build context. + static DASTableTheme? of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } + + @override + bool updateShouldNotify(DASTableTheme oldWidget) => data != oldWidget.data; +} diff --git a/das_client/lib/logging/src/log_service.dart b/das_client/lib/logging/src/log_service.dart index 2429df5e..c9586772 100644 --- a/das_client/lib/logging/src/log_service.dart +++ b/das_client/lib/logging/src/log_service.dart @@ -41,7 +41,7 @@ class LogService { void _saveInternal(LogEntry log) async { await _initialized; _lock.synchronized(() { - var lastSavedFile = File('$_logPath/$_lastSavedFileName'); + final lastSavedFile = File('$_logPath/$_lastSavedFileName'); if (!(lastSavedFile.existsSync())) { lastSavedFile.createSync(recursive: true); } @@ -59,19 +59,19 @@ class LogService { void _sendLogs() async { _senderLock.synchronized(() async { - var logDir = Directory(_logPath); - var files = logDir.listSync(); + final logDir = Directory(_logPath); + final files = logDir.listSync(); Fimber.d('Found ${files.length} log files in log directory: $_logPath'); - for (var file in files) { + for (final file in files) { if (file is File && file.path.endsWith('.json') && !file.path.contains(_lastSavedFileName)) { Fimber.d('Sending ${file.path} to backend'); var content = file.readAsStringSync(); content = '[${content.substring(0, content.length - 1)}]'; // Remove trailing comma - Iterable iterable = json.decode(content); - List logEntries = List.from(iterable.map((json) => LogEntry.fromJson(json))); + final Iterable iterable = json.decode(content); + final List logEntries = List.from(iterable.map((json) => LogEntry.fromJson(json))); final backendService = DI.get(); if (await backendService.sendLogs(logEntries)) { diff --git a/das_client/lib/mqtt/src/mqtt_client_oauth_connector.dart b/das_client/lib/mqtt/src/mqtt_client_oauth_connector.dart index 6d7ec458..d480bb4e 100644 --- a/das_client/lib/mqtt/src/mqtt_client_oauth_connector.dart +++ b/das_client/lib/mqtt/src/mqtt_client_oauth_connector.dart @@ -16,17 +16,17 @@ class MqttClientOauthConnector implements MqttClientConnector { Future connect(MqttClient client, String company, String train) async { Fimber.i('Connecting to mqtt using oauth token'); - var sferaAuthToken = await _sferaAuthService.retrieveSferaAuthToken(company, train, 'active'); + final sferaAuthToken = await _sferaAuthService.retrieveSferaAuthToken(company, train, 'active'); Fimber.i('Received sfera token=${sferaAuthToken?.substring(0, 20)}'); - var token = await _authenticator.token(); - var jsonWebToken = token.accessToken.toJwt(); - var userId = jsonWebToken.payload['preferred_username']; + final token = await _authenticator.token(); + final jsonWebToken = token.accessToken.toJwt(); + final userId = jsonWebToken.payload['preferred_username']; Fimber.i('Using userId=$userId'); if (sferaAuthToken != null && userId != null) { try { - var mqttClientConnectionStatus = await client.connect(userId, 'OAUTH~azureAd~$sferaAuthToken'); + final mqttClientConnectionStatus = await client.connect(userId, 'OAUTH~azureAd~$sferaAuthToken'); Fimber.i('mqttClientConnectionStatus=$mqttClientConnectionStatus'); if (mqttClientConnectionStatus?.state == MqttConnectionState.connected) { diff --git a/das_client/lib/mqtt/src/mqtt_client_tms_oauth_connector.dart b/das_client/lib/mqtt/src/mqtt_client_tms_oauth_connector.dart index ba96bd72..2b034f8c 100644 --- a/das_client/lib/mqtt/src/mqtt_client_tms_oauth_connector.dart +++ b/das_client/lib/mqtt/src/mqtt_client_tms_oauth_connector.dart @@ -12,12 +12,12 @@ class MqttClientTMSOauthConnector implements MqttClientConnector { Future connect(MqttClient client, String company, String train) async { Fimber.i('Connecting to TMS mqtt using oauth token'); - var sferaAuthToken = await _sferaAuthService.retrieveSferaAuthToken(company, train, 'active'); + final sferaAuthToken = await _sferaAuthService.retrieveSferaAuthToken(company, train, 'active'); Fimber.i('Received TMS sfera token=${sferaAuthToken?.substring(0, 20)}'); if (sferaAuthToken != null) { try { - var mqttClientConnectionStatus = await client.connect('JWT', 'OPENID~AzureAD_IMTS~~$sferaAuthToken'); + final mqttClientConnectionStatus = await client.connect('JWT', 'OPENID~AzureAD_IMTS~~$sferaAuthToken'); Fimber.i('mqttClientConnectionStatus=$mqttClientConnectionStatus'); if (mqttClientConnectionStatus?.state == MqttConnectionState.connected) { diff --git a/das_client/lib/service/backend_service.dart b/das_client/lib/service/backend_service.dart index 8d13600a..b54509f1 100644 --- a/das_client/lib/service/backend_service.dart +++ b/das_client/lib/service/backend_service.dart @@ -18,16 +18,16 @@ class BackendService { Fimber.i('Trying to send logs to backend...'); final url = Uri.parse('$_baseUrl/api/v1/logging/logs'); - var authToken = await _authenticator.token(); + final authToken = await _authenticator.token(); - var response = await http.post(url, + final response = await http.post(url, headers: { 'Content-Type': 'application/json', 'Authorization': '${authToken.tokenType} ${authToken.accessToken.value}', }, body: jsonEncode(logs)); - var statusCode = response.statusCode; + final statusCode = response.statusCode; if (statusCode >= 200 && statusCode < 300) { Fimber.i('Successfully sent logs to backend'); return true; diff --git a/das_client/lib/sfera/src/model/sfera_xml_element.dart b/das_client/lib/sfera/src/model/sfera_xml_element.dart index f971b1b4..675f315f 100644 --- a/das_client/lib/sfera/src/model/sfera_xml_element.dart +++ b/das_client/lib/sfera/src/model/sfera_xml_element.dart @@ -80,7 +80,7 @@ class SferaXmlElement { attributes.forEach((k, v) { builder.attribute(k, v); }); - for (var child in children) { + for (final child in children) { child.buildElement(builder); } if (value != null) { diff --git a/das_client/lib/sfera/src/repo/sfera_repository_impl.dart b/das_client/lib/sfera/src/repo/sfera_repository_impl.dart index e09dd69a..2a1aa298 100644 --- a/das_client/lib/sfera/src/repo/sfera_repository_impl.dart +++ b/das_client/lib/sfera/src/repo/sfera_repository_impl.dart @@ -24,9 +24,9 @@ class SferaRepositoryImpl implements SferaRepository { Future saveJourneyProfile(JourneyProfile journeyProfile) async { await _initialized; - var now = DateTime.now(); - var today = DateTime(now.year, now.month, now.day); - var existingProfile = await findJourneyProfile( + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final existingProfile = await findJourneyProfile( journeyProfile.trainIdentification.otnId.company, journeyProfile.trainIdentification.otnId.operationalTrainNumber, today); // Temporary fix, because our backend does not return correct date in Journey Profile @@ -46,7 +46,7 @@ class SferaRepositoryImpl implements SferaRepository { Future saveSegmentProfile(SegmentProfile segmentProfile) async { await _initialized; - var existingProfile = + final existingProfile = await findSegmentProfile(segmentProfile.id, segmentProfile.versionMajor, segmentProfile.versionMinor); if (existingProfile == null) { final segmentProfileEntity = segmentProfile.toEntity(isarId: _db.segmentProfile.autoIncrement()); diff --git a/das_client/lib/sfera/src/service/handler/journey_profile_reply_handler.dart b/das_client/lib/sfera/src/service/handler/journey_profile_reply_handler.dart index 7ef93a55..57dbf420 100644 --- a/das_client/lib/sfera/src/service/handler/journey_profile_reply_handler.dart +++ b/das_client/lib/sfera/src/service/handler/journey_profile_reply_handler.dart @@ -14,7 +14,7 @@ class JourneyProfileReplyHandler implements SferaMessageHandler { } Fimber.i('Updating journey profiles...'); - for (var journeyProfile in message.payload!.journeyProfiles) { + for (final journeyProfile in message.payload!.journeyProfiles) { await _sferaRepository.saveJourneyProfile(journeyProfile); } diff --git a/das_client/lib/sfera/src/service/handler/segment_profile_reply_handler.dart b/das_client/lib/sfera/src/service/handler/segment_profile_reply_handler.dart index 554d8b80..0266502f 100644 --- a/das_client/lib/sfera/src/service/handler/segment_profile_reply_handler.dart +++ b/das_client/lib/sfera/src/service/handler/segment_profile_reply_handler.dart @@ -14,7 +14,7 @@ class SegmentProfileReplyHandler implements SferaMessageHandler { } Fimber.i('Updating segment profiles...'); - for (var segmentProfile in message.payload!.segmentProfiles) { + for (final segmentProfile in message.payload!.segmentProfiles) { await _sferaRepository.saveSegmentProfile(segmentProfile); } diff --git a/das_client/lib/sfera/src/service/sfera_auth_service.dart b/das_client/lib/sfera/src/service/sfera_auth_service.dart index 27c07f9a..14f2d77a 100644 --- a/das_client/lib/sfera/src/service/sfera_auth_service.dart +++ b/das_client/lib/sfera/src/service/sfera_auth_service.dart @@ -16,12 +16,12 @@ class SferaAuthService { Fimber.i('Trying to fetch sfera auth token for ru=$ru train=$train role=$role...'); final url = Uri.parse('$_tokenExchangeUrl?ru=$ru&train=$train&role=$role'); - var authToken = await _authenticator.token(); + final authToken = await _authenticator.token(); - var response = await http.get(url, headers: { + final response = await http.get(url, headers: { 'Authorization': '${authToken.tokenType} ${authToken.accessToken.value}', }); - var statusCode = response.statusCode; + final statusCode = response.statusCode; if (statusCode >= 200 && statusCode < 300) { Fimber.i('Successfully retrieved sfera auth token'); return response.body; diff --git a/das_client/lib/sfera/src/service/sfera_service_impl.dart b/das_client/lib/sfera/src/service/sfera_service_impl.dart index f4127b6a..cdaf0d4d 100644 --- a/das_client/lib/sfera/src/service/sfera_service_impl.dart +++ b/das_client/lib/sfera/src/service/sfera_service_impl.dart @@ -49,13 +49,13 @@ class SferaServiceImpl implements SferaService { void _init() { _mqttStreamSubscription = _mqttService.messageStream.listen((message) async { - var sferaG2bReplyMessage = SferaReplyParser.parse(message); + final sferaG2bReplyMessage = SferaReplyParser.parse(message); if (!sferaG2bReplyMessage.validate()) { Fimber.w('Validation failed for MQTT response'); } var handled = false; - for (var handler in List.from(_messageHandlers)) { + for (final handler in List.from(_messageHandlers)) { handled |= await handler.handleMessage(sferaG2bReplyMessage); } @@ -76,7 +76,7 @@ class SferaServiceImpl implements SferaService { if (await _mqttService.connect( otnId.company, SferaService.sferaTrain(otnId.operationalTrainNumber, otnId.startDate))) { _stateSubject.add(SferaServiceState.handshaking); - var handshakeTask = HandshakeTask(mqttService: _mqttService, otnId: otnId); + final handshakeTask = HandshakeTask(mqttService: _mqttService, otnId: otnId); _messageHandlers.add(handshakeTask); handshakeTask.execute(onTaskCompleted, onTaskFailed); } else { @@ -91,13 +91,13 @@ class SferaServiceImpl implements SferaService { Fimber.i('Task $task completed'); if (task is HandshakeTask) { _stateSubject.add(SferaServiceState.loadingJourney); - var requestJourneyTask = + final requestJourneyTask = RequestJourneyProfileTask(mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: otnId!); _messageHandlers.add(requestJourneyTask); requestJourneyTask.execute(onTaskCompleted, onTaskFailed); } else if (task is RequestJourneyProfileTask) { _stateSubject.add(SferaServiceState.loadingSegments); - var requestSegmentProfilesTask = RequestSegmentProfilesTask( + final requestSegmentProfilesTask = RequestSegmentProfilesTask( mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: otnId!, journeyProfile: data); _journeyProfileSubject.add(data); _messageHandlers.add(requestSegmentProfilesTask); @@ -111,10 +111,10 @@ class SferaServiceImpl implements SferaService { Future _refreshSegmentProfiles() async { if (_journeyProfileSubject.value != null) { - var segments = []; + final segments = []; - for (var element in _journeyProfileSubject.value!.segmentProfilesLists) { - var segmentProfileEntity = + for (final element in _journeyProfileSubject.value!.segmentProfilesLists) { + final segmentProfileEntity = await _sferaRepository.findSegmentProfile(element.spId, element.versionMajor, element.versionMinor); if (segmentProfileEntity != null) { segments.add(segmentProfileEntity.toDomain()); diff --git a/das_client/lib/sfera/src/service/task/handshake_task.dart b/das_client/lib/sfera/src/service/task/handshake_task.dart index 555ef40e..b45645e9 100644 --- a/das_client/lib/sfera/src/service/task/handshake_task.dart +++ b/das_client/lib/sfera/src/service/task/handshake_task.dart @@ -32,17 +32,17 @@ class HandshakeTask extends SferaTask { } Future _sendHandshakeRequest() async { - var sferaTrain = SferaService.sferaTrain(otnId.operationalTrainNumber, otnId.startDate); + final sferaTrain = SferaService.sferaTrain(otnId.operationalTrainNumber, otnId.startDate); Fimber.i('Sending handshake request for company=${otnId.company} train=$sferaTrain'); - var handshakeRequest = HandshakeRequest.create([ + final handshakeRequest = HandshakeRequest.create([ DasOperatingModesSupported.create( DasDrivingMode.readOnly, DasArchitecture.boardAdviceCalculation, DasConnectivity.connected), ], relatedTrainRequestType: RelatedTrainRequestType.ownTrainAndRelatedTrains, statusReportsEnabled: false); - var sferaB2gRequestMessage = + final sferaB2gRequestMessage = SferaB2gRequestMessage.create(await SferaService.messageHeader(sender: otnId.company), handshakeRequest: handshakeRequest); - var success = + final success = _mqttService.publishMessage(otnId.company, sferaTrain, sferaB2gRequestMessage.buildDocument().toString()); if (!success) { diff --git a/das_client/lib/sfera/src/service/task/request_journey_profile_task.dart b/das_client/lib/sfera/src/service/task/request_journey_profile_task.dart index 582beea7..7d2479d4 100644 --- a/das_client/lib/sfera/src/service/task/request_journey_profile_task.dart +++ b/das_client/lib/sfera/src/service/task/request_journey_profile_task.dart @@ -36,10 +36,10 @@ class RequestJourneyProfileTask extends SferaTask { } Future _requestJourneyProfile() async { - var trainIdentification = TrainIdentification.create(otnId: otnId); - var jpRequest = JpRequest.create(trainIdentification); + final trainIdentification = TrainIdentification.create(otnId: otnId); + final jpRequest = JpRequest.create(trainIdentification); - var sferaB2gRequestMessage = SferaB2gRequestMessage.create( + final sferaB2gRequestMessage = SferaB2gRequestMessage.create( await SferaService.messageHeader(trainIdentification: trainIdentification, sender: otnId.company), b2gRequest: B2gRequest.createJPRequest(jpRequest)); Fimber.i('Sending journey profile request...'); @@ -64,11 +64,11 @@ class RequestJourneyProfileTask extends SferaTask { 'Received G2bReplyPayload response with ${replyMessage.payload!.journeyProfiles.length} JourneyProfiles and ${replyMessage.payload!.segmentProfiles.length} SegmentProfiles...', ); - for (var element in replyMessage.payload!.segmentProfiles) { + for (final element in replyMessage.payload!.segmentProfiles) { await _sferaRepository.saveSegmentProfile(element); } - for (var journeyProfile in replyMessage.payload!.journeyProfiles) { + for (final journeyProfile in replyMessage.payload!.journeyProfiles) { await _sferaRepository.saveJourneyProfile(journeyProfile); } diff --git a/das_client/lib/sfera/src/service/task/request_segment_profiles_task.dart b/das_client/lib/sfera/src/service/task/request_segment_profiles_task.dart index a0337705..26007cd5 100644 --- a/das_client/lib/sfera/src/service/task/request_segment_profiles_task.dart +++ b/das_client/lib/sfera/src/service/task/request_segment_profiles_task.dart @@ -42,21 +42,21 @@ class RequestSegmentProfilesTask extends SferaTask> { } Future _requestSegmentProfiles() async { - var missingSp = await findMissingSegmentProfiles(); + final missingSp = await findMissingSegmentProfiles(); if (missingSp.isEmpty) { Fimber.i('No missing SegmentProfiles found...'); _taskCompletedCallback(this, []); return; } - List spRequests = []; - for (var sp in missingSp) { + final List spRequests = []; + for (final sp in missingSp) { spRequests.add(SpRequest.create( id: sp.spId, versionMajor: sp.versionMajor, versionMinor: sp.versionMinor, spZone: sp.spZone)); } - var trainIdentification = TrainIdentification.create(otnId: otnId); - var sferaB2gRequestMessage = SferaB2gRequestMessage.create( + final trainIdentification = TrainIdentification.create(otnId: otnId); + final sferaB2gRequestMessage = SferaB2gRequestMessage.create( await SferaService.messageHeader(trainIdentification: trainIdentification, sender: otnId.company), b2gRequest: B2gRequest.createSPRequest(spRequests)); Fimber.i('Sending segment profiles request...'); @@ -67,10 +67,10 @@ class RequestSegmentProfilesTask extends SferaTask> { } Future> findMissingSegmentProfiles() async { - var missingSps = []; + final missingSps = []; - for (var segment in journeyProfile.segmentProfilesLists) { - var existingProfile = + for (final segment in journeyProfile.segmentProfilesLists) { + final existingProfile = await _sferaRepository.findSegmentProfile(segment.spId, segment.versionMajor, segment.versionMinor); if (existingProfile == null) { missingSps.add(segment); @@ -90,7 +90,7 @@ class RequestSegmentProfilesTask extends SferaTask> { bool allValid = true; - for (var element in replyMessage.payload!.segmentProfiles) { + for (final element in replyMessage.payload!.segmentProfiles) { if (element.status == SpStatus.valid) { await _sferaRepository.saveSegmentProfile(element); } else { diff --git a/das_client/lib/sfera/src/sfera_reply_parser.dart b/das_client/lib/sfera/src/sfera_reply_parser.dart index 8cda0dd1..97076363 100644 --- a/das_client/lib/sfera/src/sfera_reply_parser.dart +++ b/das_client/lib/sfera/src/sfera_reply_parser.dart @@ -28,25 +28,25 @@ class SferaReplyParser { SferaReplyParser._(); static T parse(String input) { - var xmlDocument = XmlDocument.parse(input); - var xml = _parseXml(xmlDocument.rootElement); + final xmlDocument = XmlDocument.parse(input); + final xml = _parseXml(xmlDocument.rootElement); return xml as T; } static SferaXmlElement _parseXml(XmlElement xmlElement) { - var attributes = {}; - var children = []; + final attributes = {}; + final children = []; - for (var attribute in xmlElement.attributes) { + for (final attribute in xmlElement.attributes) { attributes[attribute.name.toString()] = attribute.value; } - for (var childElement in xmlElement.childElements) { - var child = _parseXml(childElement); + for (final childElement in xmlElement.childElements) { + final child = _parseXml(childElement); children.add(child); } - var xmlTextElements = xmlElement.children.whereType(); + final xmlTextElements = xmlElement.children.whereType(); return _createResolvedType(xmlElement.name.toString(), Map.unmodifiable(attributes), List.unmodifiable(children), xmlTextElements.length == 1 ? xmlTextElements.first.toString() : null); diff --git a/das_client/test/logging/logging_service_test.dart b/das_client/test/logging/logging_service_test.dart index 417ae413..d40e9a37 100644 --- a/das_client/test/logging/logging_service_test.dart +++ b/das_client/test/logging/logging_service_test.dart @@ -22,7 +22,7 @@ import 'logging_service_test.mocks.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late Directory logDirectory; - late MockBackendService mockBackendService = MockBackendService(); + final mockBackendService = MockBackendService(); GetIt.I.registerSingleton(mockBackendService); Fimber.plantTree(DebugTree()); @@ -38,12 +38,12 @@ void main() { }); test('Test writes logs to new file in directory', () async { - LogService loggingService = LogService(); + final loggingService = LogService(); var files = logDirectory.listSync(); expect(files, hasLength(0)); - var logEntry = LogEntry('Test message', LogLevel.info, {}); + final logEntry = LogEntry('Test message', LogLevel.info, {}); loggingService.save(logEntry); await Future.delayed(const Duration(milliseconds: 30)); @@ -52,12 +52,12 @@ void main() { }); test('Test writes appends comma after writing log', () async { - LogService loggingService = LogService(); + final loggingService = LogService(); var files = logDirectory.listSync(); expect(files, hasLength(0)); - var logEntry = LogEntry('Test message', LogLevel.fatal, {}); + final logEntry = LogEntry('Test message', LogLevel.fatal, {}); loggingService.save(logEntry); await Future.delayed(const Duration(milliseconds: 30)); @@ -67,7 +67,7 @@ void main() { }); test('Test writes multiple logs to same file', () async { - LogService loggingService = LogService(); + final loggingService = LogService(); var files = logDirectory.listSync(); expect(files, hasLength(0)); @@ -94,7 +94,7 @@ void main() { }); test('Test rolls over files after size limit reached', () async { - LogService loggingService = LogService(); + final loggingService = LogService(); when(mockBackendService.sendLogs(any)).thenAnswer((input) => Future.value(false)); var files = logDirectory.listSync(); @@ -120,7 +120,7 @@ void main() { }); test('Test send logs after rolling over file', () async { - LogService loggingService = LogService(); + final loggingService = LogService(); when(mockBackendService.sendLogs(any)).thenAnswer((input) => Future.value(false)); var files = logDirectory.listSync(); @@ -146,7 +146,7 @@ void main() { }); test('Test file deletion after successful backend request', () async { - LogService loggingService = LogService(); + final loggingService = LogService(); when(mockBackendService.sendLogs(any)).thenAnswer((input) => Future.value(true)); var files = logDirectory.listSync(); diff --git a/das_client/test/sfera/model/sfera_document_test.dart b/das_client/test/sfera/model/sfera_document_test.dart index 72fd22b3..ad6dbdf4 100644 --- a/das_client/test/sfera/model/sfera_document_test.dart +++ b/das_client/test/sfera/model/sfera_document_test.dart @@ -21,7 +21,7 @@ void main() { test('Test child elements are unmodifiable', () async { final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var parsedMessage = SferaReplyParser.parse(file.readAsStringSync()); + final parsedMessage = SferaReplyParser.parse(file.readAsStringSync()); expect(() => parsedMessage.children.add(SferaXmlElement(type: 'dummy')), throwsA(isA())); }); @@ -29,7 +29,7 @@ void main() { test('Test attributes are unmodifiable', () async { final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var parsedMessage = SferaReplyParser.parse(file.readAsStringSync()); + final parsedMessage = SferaReplyParser.parse(file.readAsStringSync()); expect(() => parsedMessage.attributes['dummy'] = 'dummy', throwsA(isA())); }); @@ -37,7 +37,7 @@ void main() { test('Test SferaReplyParser with SFERA_G2B_Reply_JP_request_9232.xml', () async { final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); expect(sferaG2bReplyMessage, isA()); expect(sferaG2bReplyMessage.type, SferaG2bReplyMessage.elementType); expect(sferaG2bReplyMessage.validate(), true); @@ -46,7 +46,7 @@ void main() { expect(sferaG2bReplyMessage.messageHeader.recipient, '1088'); expect(sferaG2bReplyMessage.payload, isNotNull); - var payload = sferaG2bReplyMessage.payload!; + final payload = sferaG2bReplyMessage.payload!; expect(payload.journeyProfiles, hasLength(1)); expect(payload.journeyProfiles.first.segmentProfilesLists, hasLength(23)); @@ -72,21 +72,21 @@ void main() { }); test('Test Sfera HandshakeRequest generation', () async { - var handshakeRequest = HandshakeRequest.create([ + final handshakeRequest = HandshakeRequest.create([ DasOperatingModesSupported.create( DasDrivingMode.goa1, DasArchitecture.boardAdviceCalculation, DasConnectivity.connected), DasOperatingModesSupported.create( DasDrivingMode.goa1, DasArchitecture.boardAdviceCalculation, DasConnectivity.standalone) ], relatedTrainRequestType: RelatedTrainRequestType.ownTrainAndOrRelatedTrains, statusReportsEnabled: true); - var messageHeader = MessageHeader.create( + final messageHeader = MessageHeader.create( 'a24e63c3-ab2e-4102-9a10-ba058dec5efe', '2019-09-26T20:07:36Z', 'DAS', 'TMS', '1084', '0084'); - var sferaB2gRequestMessage = SferaB2gRequestMessage.create(messageHeader, handshakeRequest: handshakeRequest); + final sferaB2gRequestMessage = SferaB2gRequestMessage.create(messageHeader, handshakeRequest: handshakeRequest); expect(sferaB2gRequestMessage.validate(), true); - var xmlDocument = sferaB2gRequestMessage.buildDocument(); - var sferaB2gRequestMessageString = xmlDocument.toXmlString(pretty: true, newLine: '\r\n', indent: '\t'); + final xmlDocument = sferaB2gRequestMessage.buildDocument(); + final sferaB2gRequestMessageString = xmlDocument.toXmlString(pretty: true, newLine: '\r\n', indent: '\t'); final file = File('test_resources/SFERA_B2G_RequestMessage_handshake.xml'); final xmlFileString = file.readAsStringSync(); @@ -96,7 +96,7 @@ void main() { test('Test SferaReplyParser with SFERA_G2B_ReplyMessage_handshake.xml', () async { final file = File('test_resources/SFERA_G2B_ReplyMessage_handshake.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); expect(sferaG2bReplyMessage, isA()); expect(sferaG2bReplyMessage.type, SferaG2bReplyMessage.elementType); expect(sferaG2bReplyMessage.validate(), true); @@ -105,7 +105,7 @@ void main() { expect(sferaG2bReplyMessage.messageHeader.recipient, '1084'); expect(sferaG2bReplyMessage.handshakeAcknowledgement, isNotNull); - var handshakeAcknowledgement = sferaG2bReplyMessage.handshakeAcknowledgement!; + final handshakeAcknowledgement = sferaG2bReplyMessage.handshakeAcknowledgement!; expect(handshakeAcknowledgement.operationModeSelected.architecture, DasArchitecture.boardAdviceCalculation); expect(handshakeAcknowledgement.operationModeSelected.connectivity, DasConnectivity.connected); @@ -114,7 +114,7 @@ void main() { test('Test SferaReplyParser with SFERA_G2B_ReplyMessage_handshake_rejected.xml', () async { final file = File('test_resources/SFERA_G2B_ReplyMessage_handshake_rejected.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); expect(sferaG2bReplyMessage, isA()); expect(sferaG2bReplyMessage.type, SferaG2bReplyMessage.elementType); expect(sferaG2bReplyMessage.validate(), true); @@ -124,7 +124,7 @@ void main() { expect(sferaG2bReplyMessage.handshakeAcknowledgement, isNull); expect(sferaG2bReplyMessage.handshakeReject, isNotNull); - var handshakeReject = sferaG2bReplyMessage.handshakeReject!; + final handshakeReject = sferaG2bReplyMessage.handshakeReject!; expect(handshakeReject.handshakeRejectReason, HandshakeRejectReason.connectivityNotSupported); }); diff --git a/das_client/test/sfera/service/sfera_handshake_task_test.dart b/das_client/test/sfera/service/sfera_handshake_task_test.dart index 57156b96..9f42e0dd 100644 --- a/das_client/test/sfera/service/sfera_handshake_task_test.dart +++ b/das_client/test/sfera/service/sfera_handshake_task_test.dart @@ -27,7 +27,7 @@ void main() { test('Test handshake successful', () async { when(mqttService.publishMessage(any, any, any)).thenReturn(true); - var handshakeTask = HandshakeTask(mqttService: mqttService, otnId: otnId); + final handshakeTask = HandshakeTask(mqttService: mqttService, otnId: otnId); await handshakeTask.execute((task, data) { expect(task, handshakeTask); @@ -39,16 +39,16 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); final file = File('test_resources/SFERA_G2B_ReplyMessage_handshake.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var result = await handshakeTask.handleMessage(sferaG2bReplyMessage); + final result = await handshakeTask.handleMessage(sferaG2bReplyMessage); expect(result, true); }); test('Test handshake reject', () async { when(mqttService.publishMessage(any, any, any)).thenReturn(true); - var handshakeTask = HandshakeTask(mqttService: mqttService, otnId: otnId); + final handshakeTask = HandshakeTask(mqttService: mqttService, otnId: otnId); await handshakeTask.execute((task, data) { fail('Task should not be sucessful'); @@ -59,16 +59,16 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); final file = File('test_resources/SFERA_G2B_ReplyMessage_handshake_rejected.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var result = await handshakeTask.handleMessage(sferaG2bReplyMessage); + final result = await handshakeTask.handleMessage(sferaG2bReplyMessage); expect(result, true); }); test('Test handshake ignore other messages', () async { when(mqttService.publishMessage(any, any, any)).thenReturn(true); - var handshakeTask = HandshakeTask(mqttService: mqttService, otnId: otnId); + final handshakeTask = HandshakeTask(mqttService: mqttService, otnId: otnId); await handshakeTask.execute((task, data) { fail('Test should not call success'); @@ -79,16 +79,16 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var result = await handshakeTask.handleMessage(sferaG2bReplyMessage); + final result = await handshakeTask.handleMessage(sferaG2bReplyMessage); expect(result, false); }); test('Test handshake timeout', () async { when(mqttService.publishMessage(any, any, any)).thenReturn(true); - var handshakeTask = HandshakeTask(mqttService: mqttService, otnId: otnId, timeout: const Duration(seconds: 1)); + final handshakeTask = HandshakeTask(mqttService: mqttService, otnId: otnId, timeout: const Duration(seconds: 1)); var timeoutReached = false; await handshakeTask.execute((task, data) { diff --git a/das_client/test/sfera/service/sfera_request_journey_profile_task_test.dart b/das_client/test/sfera/service/sfera_request_journey_profile_task_test.dart index 52ae60a8..0205f6f9 100644 --- a/das_client/test/sfera/service/sfera_request_journey_profile_task_test.dart +++ b/das_client/test/sfera/service/sfera_request_journey_profile_task_test.dart @@ -31,9 +31,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var journeyTask = RequestJourneyProfileTask(mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId); + final journeyTask = RequestJourneyProfileTask(mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId); await journeyTask.execute((task, data) { expect(task, journeyTask); @@ -44,7 +44,7 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); - var result = await journeyTask.handleMessage(sferaG2bReplyMessage); + final result = await journeyTask.handleMessage(sferaG2bReplyMessage); expect(result, true); }); @@ -52,9 +52,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var journeyTask = RequestJourneyProfileTask(mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId); + final journeyTask = RequestJourneyProfileTask(mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId); await journeyTask.execute((task, data) { expect(task, journeyTask); @@ -64,7 +64,7 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); - var result = await journeyTask.handleMessage(sferaG2bReplyMessage); + final result = await journeyTask.handleMessage(sferaG2bReplyMessage); expect(result, true); verify(sferaRepository.saveJourneyProfile(any)).called(1); @@ -75,9 +75,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_jp.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var journeyTask = RequestJourneyProfileTask( + final journeyTask = RequestJourneyProfileTask( mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId); @@ -91,7 +91,7 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); - var result = await journeyTask.handleMessage(sferaG2bReplyMessage); + final result = await journeyTask.handleMessage(sferaG2bReplyMessage); expect(result, true); verifyNever(sferaRepository.saveJourneyProfile(any)); @@ -102,9 +102,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232_unavailable_jp.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var journeyTask = RequestJourneyProfileTask( + final journeyTask = RequestJourneyProfileTask( mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId); @@ -118,7 +118,7 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); - var result = await journeyTask.handleMessage(sferaG2bReplyMessage); + final result = await journeyTask.handleMessage(sferaG2bReplyMessage); expect(result, true); verifyNever(sferaRepository.saveJourneyProfile(any)); @@ -128,7 +128,7 @@ void main() { test('Test JP request ignores other messages', () async { when(mqttService.publishMessage(any, any, any)).thenReturn(true); - var journeyTask = RequestJourneyProfileTask(mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId); + final journeyTask = RequestJourneyProfileTask(mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId); await journeyTask.execute((task, data) { fail('Test should not call success'); @@ -139,16 +139,16 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); final file = File('test_resources/SFERA_G2B_ReplyMessage_handshake.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var result = await journeyTask.handleMessage(sferaG2bReplyMessage); + final result = await journeyTask.handleMessage(sferaG2bReplyMessage); expect(result, false); }); test('Test request journey profile timeout', () async { when(mqttService.publishMessage(any, any, any)).thenReturn(true); - var journeyTask = RequestJourneyProfileTask( + final journeyTask = RequestJourneyProfileTask( mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId, timeout: const Duration(seconds: 1)); var timeoutReached = false; diff --git a/das_client/test/sfera/service/sfera_request_segment_profile_task_test.dart b/das_client/test/sfera/service/sfera_request_segment_profile_task_test.dart index 7eb8d908..3cc5d1e3 100644 --- a/das_client/test/sfera/service/sfera_request_segment_profile_task_test.dart +++ b/das_client/test/sfera/service/sfera_request_segment_profile_task_test.dart @@ -31,9 +31,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var segmentTask = RequestSegmentProfilesTask( + final segmentTask = RequestSegmentProfilesTask( mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId, @@ -48,7 +48,7 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); - var result = await segmentTask.handleMessage(sferaG2bReplyMessage); + final result = await segmentTask.handleMessage(sferaG2bReplyMessage); expect(result, true); }); @@ -56,9 +56,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var segmentTask = RequestSegmentProfilesTask( + final segmentTask = RequestSegmentProfilesTask( mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId, @@ -72,7 +72,7 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); - var result = await segmentTask.handleMessage(sferaG2bReplyMessage); + final result = await segmentTask.handleMessage(sferaG2bReplyMessage); expect(result, true); verifyNever(sferaRepository.saveJourneyProfile(any)); @@ -83,9 +83,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_sp.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var segmentTask = RequestSegmentProfilesTask( + final segmentTask = RequestSegmentProfilesTask( mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId, @@ -100,7 +100,7 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); - var result = await segmentTask.handleMessage(sferaG2bReplyMessage); + final result = await segmentTask.handleMessage(sferaG2bReplyMessage); expect(result, true); verifyNever(sferaRepository.saveJourneyProfile(any)); @@ -111,9 +111,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var segmentTask = RequestSegmentProfilesTask( + final segmentTask = RequestSegmentProfilesTask( mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId, @@ -128,8 +128,8 @@ void main() { verify(mqttService.publishMessage(any, any, any)).called(1); final handShakefile = File('test_resources/SFERA_G2B_ReplyMessage_handshake.xml'); - var handshakeSferaG2bReplyMessage = SferaReplyParser.parse(handShakefile.readAsStringSync()); - var result = await segmentTask.handleMessage(handshakeSferaG2bReplyMessage); + final handshakeSferaG2bReplyMessage = SferaReplyParser.parse(handShakefile.readAsStringSync()); + final result = await segmentTask.handleMessage(handshakeSferaG2bReplyMessage); expect(result, false); }); @@ -137,9 +137,9 @@ void main() { when(mqttService.publishMessage(any, any, any)).thenReturn(true); final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); - var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + final sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); - var journeyTask = RequestSegmentProfilesTask( + final journeyTask = RequestSegmentProfilesTask( mqttService: mqttService, sferaRepository: sferaRepository, otnId: otnId,