From 1c6cc8df7b4f38e9c76ae9ee9a848d908365320b Mon Sep 17 00:00:00 2001 From: u221638 Date: Wed, 4 Dec 2024 17:20:24 +0100 Subject: [PATCH 01/22] feat: added CAB start and end signaling --- das_client/analysis_options.yaml | 21 +- das_client/assets/icons/icon_cab_end.svg | 5 + das_client/assets/icons/icon_cab_start.svg | 4 + das_client/ios/Podfile.lock | 2 +- .../lib/app/bloc/train_journey_cubit.dart | 2 +- .../lib/app/bloc/train_journey_state.dart | 2 +- .../widgets/header/adl_notification.dart | 2 +- .../additional_speed_restriction_row.dart | 16 +- .../widgets/table/base_row_builder.dart | 8 +- .../widgets/table/cab_signaling_row.dart | 27 +++ .../table/cells/bracket_station_body.dart | 4 +- .../widgets/table/cells/route_cell_body.dart | 13 +- .../widgets/table/protection_section_row.dart | 3 +- .../widgets/table/service_point_row.dart | 6 +- .../train_journey/widgets/train_journey.dart | 7 + .../lib/app/widgets/app_version_text.dart | 2 +- das_client/lib/app/widgets/assets.dart | 2 + .../lib/app/widgets/device_id_text.dart | 2 +- das_client/lib/app/widgets/header.dart | 2 +- .../lib/app/widgets/table/das_table.dart | 34 +-- .../lib/app/widgets/table/das_table_row.dart | 10 +- .../app/widgets/table/das_table_theme.dart | 2 +- .../lib/auth/src/authenticator_config.dart | 2 +- das_client/lib/flavor.dart | 6 +- das_client/lib/model/journey/base_data.dart | 3 - .../lib/model/journey/cab_signaling.dart | 12 + das_client/lib/model/journey/curve_point.dart | 1 - das_client/lib/model/journey/datatype.dart | 1 + das_client/lib/model/journey/metadata.dart | 17 +- .../lib/model/journey/service_point.dart | 3 +- das_client/lib/model/journey/signal.dart | 1 - .../lib/model/journey/track_equipment.dart | 89 +++++--- .../sfera/src/mapper/sfera_model_mapper.dart | 117 ++++------ .../src/mapper/track_equipment_mapper.dart | 143 ++++++++++++ .../src/model/enums/track_equipment_type.dart | 29 +++ .../src/model/network_specific_area.dart | 5 +- .../src/model/network_specific_parameter.dart | 9 + .../lib/sfera/src/model/segment_profile.dart | 12 + .../model/track_equipment_type_wrapper.dart | 16 ++ .../lib/sfera/src/service/sfera_service.dart | 2 +- .../lib/sfera/src/sfera_reply_parser.dart | 2 +- .../model/journey/track_equipment_test.dart | 210 ++++++++++++------ .../test/sfera/mapper/sfera_mapper_test.dart | 171 +++++++------- .../test_resources/jp/SFERA_JP_9999.xml | 11 +- .../test_resources/sp/SFERA_SP_9999_1.xml | 16 +- .../test_resources/sp/SFERA_SP_9999_2.xml | 16 +- .../test_resources/sp/SFERA_SP_9999_3.xml | 20 +- .../test_resources/sp/SFERA_SP_9999_4.xml | 30 ++- .../test_resources/sp/SFERA_SP_9999_5.xml | 35 ++- 49 files changed, 769 insertions(+), 386 deletions(-) create mode 100644 das_client/assets/icons/icon_cab_end.svg create mode 100644 das_client/assets/icons/icon_cab_start.svg create mode 100644 das_client/lib/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart create mode 100644 das_client/lib/model/journey/cab_signaling.dart create mode 100644 das_client/lib/sfera/src/mapper/track_equipment_mapper.dart create mode 100644 das_client/lib/sfera/src/model/enums/track_equipment_type.dart create mode 100644 das_client/lib/sfera/src/model/track_equipment_type_wrapper.dart diff --git a/das_client/analysis_options.yaml b/das_client/analysis_options.yaml index 50970900..216c42d8 100644 --- a/das_client/analysis_options.yaml +++ b/das_client/analysis_options.yaml @@ -1,32 +1,13 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: always_use_package_imports: true prefer_single_quotes: true prefer_final_in_for_each: true prefer_final_locals: true prefer_final_fields: true + always_put_required_named_parameters_first: true # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/das_client/assets/icons/icon_cab_end.svg b/das_client/assets/icons/icon_cab_end.svg new file mode 100644 index 00000000..57beb212 --- /dev/null +++ b/das_client/assets/icons/icon_cab_end.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/das_client/assets/icons/icon_cab_start.svg b/das_client/assets/icons/icon_cab_start.svg new file mode 100644 index 00000000..b08d80b3 --- /dev/null +++ b/das_client/assets/icons/icon_cab_start.svg @@ -0,0 +1,4 @@ + + + + diff --git a/das_client/ios/Podfile.lock b/das_client/ios/Podfile.lock index 0ef49177..2e39e64c 100644 --- a/das_client/ios/Podfile.lock +++ b/das_client/ios/Podfile.lock @@ -68,4 +68,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: d9dad56c0cd0b4fd8b4fe3034a53fd42a0b990f6 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.1 diff --git a/das_client/lib/app/bloc/train_journey_cubit.dart b/das_client/lib/app/bloc/train_journey_cubit.dart index a9988c0e..916e4da0 100644 --- a/das_client/lib/app/bloc/train_journey_cubit.dart +++ b/das_client/lib/app/bloc/train_journey_cubit.dart @@ -89,7 +89,7 @@ class TrainJourneyCubit extends Cubit { void reset() { if (state is BaseTrainJourneyState) { - Fimber.i('Reseting TrainJourney cubit in state $state'); + Fimber.i('Resetting TrainJourney cubit in state $state'); emit(SelectingTrainJourneyState( trainNumber: (state as BaseTrainJourneyState).trainNumber, date: DateTime.now(), diff --git a/das_client/lib/app/bloc/train_journey_state.dart b/das_client/lib/app/bloc/train_journey_state.dart index 818b68c0..fb2cc0c3 100644 --- a/das_client/lib/app/bloc/train_journey_state.dart +++ b/das_client/lib/app/bloc/train_journey_state.dart @@ -4,7 +4,7 @@ part of 'train_journey_cubit.dart'; sealed class TrainJourneyState {} final class SelectingTrainJourneyState extends TrainJourneyState { - SelectingTrainJourneyState({this.ru, this.trainNumber, required this.date, this.errorCode}); + SelectingTrainJourneyState({required this.date, this.ru, this.trainNumber, this.errorCode}); final String? trainNumber; final Ru? ru; 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 9fee1aeb..53c097f9 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 @@ -3,7 +3,7 @@ import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; class ADLNotification extends StatelessWidget { - const ADLNotification({super.key, required this.message, this.margin}); + const ADLNotification({required this.message, super.key, this.margin}); final String message; final EdgeInsetsGeometry? margin; diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart index ac3f72bf..cab347ef 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart @@ -8,13 +8,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class AdditionalSpeedRestrictionRow extends BaseRowBuilder { - static const Key additionalSpeedRestrictionIconKey = Key('addition_speed_restrction_icon_key'); + static const Key additionalSpeedRestrictionIconKey = Key('addition_speed_restriction_icon_key'); static const Color additionalSpeedRestrictionColor = SBBColors.orange; + static const double rowHeight = 44.0; AdditionalSpeedRestrictionRow({ - super.height = 44.0, required super.metadata, required super.data, + super.height = rowHeight, }) : super(rowColor: additionalSpeedRestrictionColor); @override @@ -33,11 +34,12 @@ class AdditionalSpeedRestrictionRow extends BaseRowBuilder extends DASTableRowBuilder { + static const double rowHeight = 44.0; + const BaseRowBuilder({ - super.height = 44.0, - this.defaultAlignment = Alignment.bottomCenter, - this.rowColor, required this.metadata, required this.data, + super.height = rowHeight, + this.defaultAlignment = Alignment.bottomCenter, + this.rowColor, }); final Alignment defaultAlignment; diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart new file mode 100644 index 00000000..2a407c32 --- /dev/null +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart @@ -0,0 +1,27 @@ +import 'package:das_client/app/pages/journey/train_journey/widgets/table/base_row_builder.dart'; +import 'package:das_client/app/widgets/assets.dart'; +import 'package:das_client/app/widgets/table/das_table_cell.dart'; +import 'package:das_client/model/journey/cab_signaling.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class CABSignalingRow extends BaseRowBuilder { + static const Key cabSignalingStartIconKey = Key('cab_signaling_start_icon_key'); + static const Key cabSignalingEndIconKey = Key('cab_signaling_end_icon_key'); + + CABSignalingRow({ + required super.metadata, + required super.data, + }); + + @override + DASTableCell iconsCell1(BuildContext context) { + return DASTableCell( + child: SvgPicture.asset( + key: data.isStart ? cabSignalingStartIconKey : cabSignalingEndIconKey, + data.isStart ? AppAssets.iconCabStart : AppAssets.iconCabEnd, + ), + alignment: Alignment.center, + ); + } +} diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart index 869e42ee..78ca13d8 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart @@ -8,9 +8,9 @@ class BracketStationBody extends StatelessWidget { static const double _bracketStationFontSize = 12.0; const BracketStationBody({ - super.key, required this.bracketStation, - required this.height + required this.height, + super.key, }); final BracketStation bracketStation; 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 index 05de9df6..f98fd752 100644 --- 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 @@ -54,11 +54,7 @@ class RouteCellBody extends StatelessWidget { final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; final lineColor = isDarkTheme ? SBBColors.white : SBBColors.black; return Positioned( - key: isRouteStart - ? routeStartKey - : isRouteEnd - ? routeEndKey - : null, + key: _routeKey(), top: isRouteStart ? height - sbbDefaultSpacing : -sbbDefaultSpacing, bottom: isRouteEnd ? sbbDefaultSpacing : -sbbDefaultSpacing, right: 0, @@ -87,6 +83,13 @@ class RouteCellBody extends StatelessWidget { ), ); } + + Key? _routeKey() { + if (!isRouteStart && !isRouteEnd) { + return null; + } + return isRouteStart ? routeStartKey : routeEndKey; + } } class _RouteCircle extends StatelessWidget { diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/protection_section_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/protection_section_row.dart index 32645ed5..21387a1d 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/protection_section_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/protection_section_row.dart @@ -9,11 +9,12 @@ import 'package:flutter_svg/flutter_svg.dart'; class ProtectionSectionRow extends BaseRowBuilder { static const Key protectionSectionKey = Key('protection_section_key'); + static const double rowHeight = 44.0; ProtectionSectionRow({ - super.height = 44.0, required super.metadata, required super.data, + super.height = rowHeight, }) : super(rowColor: SBBColors.peach); @override 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 index b1dd7bc6..4f0de35c 100644 --- 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 @@ -11,10 +11,12 @@ import 'package:flutter_svg/flutter_svg.dart'; class ServicePointRow extends BaseRowBuilder { static const Key stopOnRequestKey = Key('stop_on_request_key'); + static const double rowHeight = 64.0; + ServicePointRow({ - super.height = 64.0, required super.metadata, required super.data, + super.height = rowHeight, }) : super(rowColor: metadata.nextStop == data ? SBBColors.royal.withOpacity(0.2) : Colors.transparent); @override @@ -51,7 +53,7 @@ class ServicePointRow extends BaseRowBuilder { if (data.bracketStation != null) BracketStationBody( bracketStation: data.bracketStation!, - height: height!, + height: height, ), if (!data.mandatoryStop) Align( 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 5cecb20f..14c776d6 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,6 +1,7 @@ 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/additional_speed_restriction_row.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/curve_point_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/protection_section_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/service_point_row.dart'; @@ -9,6 +10,7 @@ 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/app/widgets/table/das_table_row.dart'; import 'package:das_client/model/journey/additional_speed_restriction_data.dart'; +import 'package:das_client/model/journey/cab_signaling.dart'; import 'package:das_client/model/journey/curve_point.dart'; import 'package:das_client/model/journey/datatype.dart'; import 'package:das_client/model/journey/journey.dart'; @@ -65,6 +67,11 @@ class TrainJourney extends StatelessWidget { return AdditionalSpeedRestrictionRow( metadata: journey.metadata, data: rowData as AdditionalSpeedRestrictionData) .build(context); + case Datatype.cabSignaling: + return CABSignalingRow( + metadata: journey.metadata, + data: rowData as CABSignaling, + ).build(context); } }); } diff --git a/das_client/lib/app/widgets/app_version_text.dart b/das_client/lib/app/widgets/app_version_text.dart index 3fc4a12d..802e361a 100644 --- a/das_client/lib/app/widgets/app_version_text.dart +++ b/das_client/lib/app/widgets/app_version_text.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; class AppVersionText extends StatelessWidget { - const AppVersionText({super.key, required this.color}); + const AppVersionText({required this.color, super.key}); final Color color; diff --git a/das_client/lib/app/widgets/assets.dart b/das_client/lib/app/widgets/assets.dart index a0c49302..ab95aca0 100644 --- a/das_client/lib/app/widgets/assets.dart +++ b/das_client/lib/app/widgets/assets.dart @@ -13,4 +13,6 @@ class AppAssets { static const iconAdditionalSpeedRestriction = '$_iconsDir/icon_additional_speed_restriction.svg'; static const iconCurveStart = '$_iconsDir/icon_curve_start.svg'; static const iconSignalLaneChange = '$_iconsDir/icon_signal_line_change.svg'; + static const iconCabStart = '$_iconsDir/icon_cab_start.svg'; + static const iconCabEnd = '$_iconsDir/icon_cab_end.svg'; } diff --git a/das_client/lib/app/widgets/device_id_text.dart b/das_client/lib/app/widgets/device_id_text.dart index 3e54d407..cb959d57 100644 --- a/das_client/lib/app/widgets/device_id_text.dart +++ b/das_client/lib/app/widgets/device_id_text.dart @@ -3,7 +3,7 @@ import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; class DeviceIdText extends StatelessWidget { - const DeviceIdText({super.key, required this.color}); + const DeviceIdText({required this.color, super.key}); final Color color; diff --git a/das_client/lib/app/widgets/header.dart b/das_client/lib/app/widgets/header.dart index 39778dcb..fa60d162 100644 --- a/das_client/lib/app/widgets/header.dart +++ b/das_client/lib/app/widgets/header.dart @@ -2,7 +2,7 @@ import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; class Header extends StatelessWidget { - const Header({super.key, required this.child}); + const Header({required this.child, super.key}); final Widget child; diff --git a/das_client/lib/app/widgets/table/das_table.dart b/das_client/lib/app/widgets/table/das_table.dart index b07cf77d..c00244b4 100644 --- a/das_client/lib/app/widgets/table/das_table.dart +++ b/das_client/lib/app/widgets/table/das_table.dart @@ -14,10 +14,11 @@ import 'package:flutter/material.dart'; @immutable class DASTable extends StatelessWidget { static const Key rowKey = Key('DAS-Table-row'); + static const double _headerRowHeight = 40.0; DASTable({ - super.key, required this.columns, + super.key, this.rows = const [], this.scrollController, this.bottomMargin = 32.0, @@ -97,7 +98,8 @@ class DASTable extends StatelessWidget { } Widget _headerRow() { - return _FlexibleHeightRow( + return _FixedHeightRow( + height: _headerRowHeight, children: columns.where((column) => column.isVisible).map((column) => _headerCell(column)).toList(), ); } @@ -130,8 +132,8 @@ class DASTable extends StatelessWidget { 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, + return _FixedHeightRow( + height: row.height, children: List.generate(visibleColumns.length, (index) { final cell = visibleCells[index]; final column = visibleColumns[index]; @@ -188,28 +190,28 @@ extension _TableBorderExtension on TableBorder { } } -/// 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}); +class _FixedHeightRow extends StatelessWidget { + const _FixedHeightRow({required this.height, required this.children}); - final double? fixedHeight; + final double height; final List children; @override Widget build(BuildContext context) { - final row = Row(key: DASTable.rowKey, crossAxisAlignment: CrossAxisAlignment.stretch, children: children); - if (fixedHeight != null) { - return SizedBox(height: fixedHeight, child: row); - } - return IntrinsicHeight(child: row); + return SizedBox( + height: height, + child: Row( + key: DASTable.rowKey, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: children, + ), + ); } } /// 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}); + const _TableCellWrapper({required this.child, this.width, this.expanded = false}); /// The fixed width for the cell. final double? width; diff --git a/das_client/lib/app/widgets/table/das_table_row.dart b/das_client/lib/app/widgets/table/das_table_row.dart index 0ebc9a51..4509c879 100644 --- a/das_client/lib/app/widgets/table/das_table_row.dart +++ b/das_client/lib/app/widgets/table/das_table_row.dart @@ -3,20 +3,20 @@ import 'package:flutter/material.dart'; /// Interface for a class that builds [DASTableRow] abstract class DASTableRowBuilder { - const DASTableRowBuilder({this.height}); + const DASTableRowBuilder({required this.height}); DASTableRow build(BuildContext context); - final double? height; + final double height; } /// Represents a row in the [DASTable] containing cells. @immutable class DASTableRow { - const DASTableRow({required this.cells, this.height, this.color}); + const DASTableRow({required this.cells, required this.height, this.color}); - /// The fixed height for the row. If null, intrinsic height will be used. - final double? height; + /// Height of the row + final double height; /// The background color for all cells of the row if not overridden by cell style. final Color? color; diff --git a/das_client/lib/app/widgets/table/das_table_theme.dart b/das_client/lib/app/widgets/table/das_table_theme.dart index a8060dd2..6c2ba261 100644 --- a/das_client/lib/app/widgets/table/das_table_theme.dart +++ b/das_client/lib/app/widgets/table/das_table_theme.dart @@ -97,9 +97,9 @@ class DASTableThemeData { /// 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, + super.key, }); /// The properties used for all descendant [DASTableTheme] widgets. diff --git a/das_client/lib/auth/src/authenticator_config.dart b/das_client/lib/auth/src/authenticator_config.dart index 84ace9cd..9f753095 100644 --- a/das_client/lib/auth/src/authenticator_config.dart +++ b/das_client/lib/auth/src/authenticator_config.dart @@ -8,8 +8,8 @@ class AuthenticatorConfig { required this.discoveryUrl, required this.clientId, required this.redirectUrl, - this.postLogoutRedirectUrl, required this.tokenSpecs, + this.postLogoutRedirectUrl, }); final String discoveryUrl; diff --git a/das_client/lib/flavor.dart b/das_client/lib/flavor.dart index bd08b93c..8d6de002 100644 --- a/das_client/lib/flavor.dart +++ b/das_client/lib/flavor.dart @@ -33,13 +33,13 @@ enum Flavor { const Flavor({ required this.displayName, required this.tokenExchangeUrl, - this.tmsTokenExchangeUrl, required this.mqttUrl, - this.tmsMqttUrl, required this.authenticatorConfig, - this.tmsAuthenticatorConfig, required this.mqttTopicPrefix, required this.backendUrl, + this.tmsTokenExchangeUrl, + this.tmsMqttUrl, + this.tmsAuthenticatorConfig, }); final String displayName; diff --git a/das_client/lib/model/journey/base_data.dart b/das_client/lib/model/journey/base_data.dart index 2966c377..1aefcb66 100644 --- a/das_client/lib/model/journey/base_data.dart +++ b/das_client/lib/model/journey/base_data.dart @@ -1,16 +1,13 @@ import 'package:das_client/model/journey/datatype.dart'; -import 'package:das_client/model/journey/track_equipment.dart'; abstract class BaseData { BaseData({ required this.type, required this.order, required this.kilometre, - this.trackEquipment = const [], }); final Datatype type; final int order; final List kilometre; - final List trackEquipment; } diff --git a/das_client/lib/model/journey/cab_signaling.dart b/das_client/lib/model/journey/cab_signaling.dart new file mode 100644 index 00000000..6a9a6cd2 --- /dev/null +++ b/das_client/lib/model/journey/cab_signaling.dart @@ -0,0 +1,12 @@ +import 'package:das_client/model/journey/base_data.dart'; +import 'package:das_client/model/journey/datatype.dart'; + +class CABSignaling extends BaseData { + CABSignaling({ + required super.order, + required super.kilometre, + this.isStart = false, + }) : super(type: Datatype.cabSignaling); + + final bool isStart; +} \ No newline at end of file diff --git a/das_client/lib/model/journey/curve_point.dart b/das_client/lib/model/journey/curve_point.dart index 53687e32..4f9822ed 100644 --- a/das_client/lib/model/journey/curve_point.dart +++ b/das_client/lib/model/journey/curve_point.dart @@ -5,7 +5,6 @@ class CurvePoint extends BaseData { CurvePoint({ required super.order, required super.kilometre, - super.trackEquipment, this.curvePointType, this.curveType, this.comment, diff --git a/das_client/lib/model/journey/datatype.dart b/das_client/lib/model/journey/datatype.dart index 46f36983..f28de183 100644 --- a/das_client/lib/model/journey/datatype.dart +++ b/das_client/lib/model/journey/datatype.dart @@ -3,5 +3,6 @@ enum Datatype { protectionSection, signal, curvePoint, + cabSignaling, additionalSpeedRestriction; } diff --git a/das_client/lib/model/journey/metadata.dart b/das_client/lib/model/journey/metadata.dart index 7d83f109..c2b2f363 100644 --- a/das_client/lib/model/journey/metadata.dart +++ b/das_client/lib/model/journey/metadata.dart @@ -1,19 +1,22 @@ import 'package:das_client/model/journey/additional_speed_restriction.dart'; import 'package:das_client/model/journey/base_data.dart'; import 'package:das_client/model/journey/service_point.dart'; +import 'package:das_client/model/journey/track_equipment.dart'; class Metadata { - Metadata( - {this.nextStop, - this.currentPosition, - this.routeStart, - this.routeEnd, - List? additionalSpeedRestrictions}) - : additionalSpeedRestrictions = additionalSpeedRestrictions ?? []; + Metadata({ + this.nextStop, + this.currentPosition, + this.routeStart, + this.routeEnd, + this.additionalSpeedRestrictions = const [], + this.nonStandardTrackEquipmentSegment = const [], + }); final ServicePoint? nextStop; final BaseData? currentPosition; final List additionalSpeedRestrictions; final BaseData? routeStart; final BaseData? routeEnd; + final List nonStandardTrackEquipmentSegment; } diff --git a/das_client/lib/model/journey/service_point.dart b/das_client/lib/model/journey/service_point.dart index 099ee620..835ba81c 100644 --- a/das_client/lib/model/journey/service_point.dart +++ b/das_client/lib/model/journey/service_point.dart @@ -9,10 +9,9 @@ class ServicePoint extends BaseData { required this.mandatoryStop, required this.isStop, required this.isStation, - this.bracketStation, required super.order, required super.kilometre, - super.trackEquipment, + this.bracketStation, }) : super(type: Datatype.servicePoint); final LocalizedString name; diff --git a/das_client/lib/model/journey/signal.dart b/das_client/lib/model/journey/signal.dart index cc55d94d..712f90d1 100644 --- a/das_client/lib/model/journey/signal.dart +++ b/das_client/lib/model/journey/signal.dart @@ -7,7 +7,6 @@ class Signal extends BaseData { required super.kilometre, this.visualIdentifier, this.functions = const [], - super.trackEquipment, }) : super(type: Datatype.signal); final List functions; diff --git a/das_client/lib/model/journey/track_equipment.dart b/das_client/lib/model/journey/track_equipment.dart index ebefbcd9..14ea01fe 100644 --- a/das_client/lib/model/journey/track_equipment.dart +++ b/das_client/lib/model/journey/track_equipment.dart @@ -1,52 +1,67 @@ -import 'package:collection/collection.dart'; - -class TrackEquipment { - TrackEquipment({ +class NonStandardTrackEquipmentSegment { + const NonStandardTrackEquipmentSegment({ + required this.startKm, + required this.endKm, required this.type, - this.startLocation, - this.endLocation, - this.appliesToWholeSp = false, + required this.startOrder, + required this.endOrder, }); - final double? startLocation; - final double? endLocation; - final bool appliesToWholeSp; + final List startKm; + final List endKm; + final int startOrder; + final int endOrder; final TrackEquipmentType type; - bool isOnLocation(double location) { - if (appliesToWholeSp) { - return true; - } else if (startLocation != null && endLocation != null) { - return startLocation! <= location && location <= endLocation!; - } else if (startLocation != null) { - return startLocation! <= location; - } else if (endLocation != null) { - return location <= endLocation!; - } - return false; - } + bool appliesToOrder(int order) => startOrder <= order && order <= endOrder; } enum TrackEquipmentType { - etcsL1ls2TracksWithSingleTrackEquipment('ETCS-L1LS-2TracksWithSingleTrackEquipment'), - etcsL2ConvSpeedReversingImpossible('ETCS-L2-convSpeedReversingImpossible'), - etcsL2ExtSpeedReversingPossible('ETCS-L2-extSpeedReversingPossible'), - etcsL2ExtSpeedReversingImpossible('ETCS-L2-extSpeedReversingImpossible'); + etcsL1ls2TracksWithSingleTrackEquipment, + etcsL2ConvSpeedReversingImpossible, + etcsL2ExtSpeedReversingPossible, + etcsL2ExtSpeedReversingImpossible; + + bool isEtcsL2() => [ + TrackEquipmentType.etcsL2ConvSpeedReversingImpossible, + TrackEquipmentType.etcsL2ExtSpeedReversingPossible, + TrackEquipmentType.etcsL2ExtSpeedReversingImpossible + ].contains(this); +} + +// extensions - const TrackEquipmentType(this.value); +extension NonStandardTrackEquipmentSegmentsExtension on List { + List appliesToOrder(int order) => + where((segment) => segment.appliesToOrder(order)).toList(); - final String value; + /// Returns all [NonStandardTrackEquipmentSegment] of this list that mark the start of a ETCS level 2 segment + List get withCABSignalingStart { + final etcsL2Segments = where((segment) => segment.type.isEtcsL2()).toList(); + etcsL2Segments.sort((a, b) => a.startOrder.compareTo(b.startOrder)); + final starts = []; - static TrackEquipmentType? from(String value) { - return values.firstWhereOrNull( - (e) => e.value.toLowerCase() == value.toLowerCase() - ); + for (int i = 0; i < etcsL2Segments.length; i++) { + if (i == 0 || etcsL2Segments[i].startOrder != etcsL2Segments[i - 1].endOrder) { + starts.add(etcsL2Segments[i]); + } + } + + return starts; } -} -// extensions + /// Returns all [NonStandardTrackEquipmentSegment] of this list that mark the end of a ETCS level 2 segment + List get withCABSignalingEnd { + final etcsL2Segments = where((segment) => segment.type.isEtcsL2()).toList(); + etcsL2Segments.sort((a, b) => a.startOrder.compareTo(b.startOrder)); + final ends = []; -extension TrackEquipmentsExtension on Iterable { - Iterable whereOnLocation(double location) => - where((trackEquipment) => trackEquipment.isOnLocation(location)); + for (int i = 0; i < etcsL2Segments.length; i++) { + if (i == etcsL2Segments.length - 1 || etcsL2Segments[i].endOrder != etcsL2Segments[i + 1].startOrder) { + ends.add(etcsL2Segments[i]); + } + } + + return ends; + } } diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart index be5a73cb..95860af2 100644 --- a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -3,6 +3,7 @@ import 'package:das_client/model/journey/additional_speed_restriction.dart'; import 'package:das_client/model/journey/additional_speed_restriction_data.dart'; import 'package:das_client/model/journey/base_data.dart'; import 'package:das_client/model/journey/bracket_station.dart'; +import 'package:das_client/model/journey/cab_signaling.dart'; import 'package:das_client/model/journey/curve_point.dart'; import 'package:das_client/model/journey/datatype.dart'; import 'package:das_client/model/journey/journey.dart'; @@ -12,6 +13,7 @@ import 'package:das_client/model/journey/service_point.dart'; import 'package:das_client/model/journey/signal.dart'; import 'package:das_client/model/journey/track_equipment.dart'; import 'package:das_client/model/localized_string.dart'; +import 'package:das_client/sfera/src/mapper/track_equipment_mapper.dart'; import 'package:das_client/sfera/src/model/enums/length_type.dart'; import 'package:das_client/sfera/src/model/enums/start_end_qualifier.dart'; import 'package:das_client/sfera/src/model/enums/stop_skip_pass.dart'; @@ -21,7 +23,6 @@ import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/multilingual_text.dart'; import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; -import 'package:das_client/sfera/src/model/segment_profile_list.dart'; import 'package:das_client/sfera/src/model/taf_tap_location.dart'; import 'package:fimber/fimber.dart'; @@ -52,22 +53,15 @@ class SferaModelMapper { for (int segmentIndex = 0; segmentIndex < segmentProfilesLists.length; segmentIndex++) { final segmentProfileList = segmentProfilesLists[segmentIndex]; - final segmentProfile = segmentProfiles - .where((it) => - it.id == segmentProfileList.spId && - it.versionMajor == segmentProfileList.versionMajor && - it.versionMinor == segmentProfileList.versionMinor) - .first; + final segmentProfile = segmentProfiles.firstMatch(segmentProfileList); - final trackEquipments = _parseTrackEquipments(segmentProfile); + final kilometreMap = parseKilometre(segmentProfile); - final kilometreMap = _parseKilometre(segmentProfile); - - final curvePoints = _parseCurvePoints(segmentProfile, segmentIndex, kilometreMap, trackEquipments); + final curvePoints = _parseCurvePoints(segmentProfile, segmentIndex, kilometreMap); final curveBeginPoints = curvePoints.where((curve) => curve.curvePointType == CurvePointType.begin); journeyData.addAll(curveBeginPoints); - final signals = _parseSignals(segmentProfile, segmentIndex, kilometreMap, trackEquipments); + final signals = _parseSignals(segmentProfile, segmentIndex, kilometreMap); journeyData.addAll(signals); final timingPoints = segmentProfile.points?.timingPoints.toList() ?? []; @@ -83,13 +77,12 @@ class SferaModelMapper { journeyData.add(ServicePoint( name: _localizedStringFromMultilingualText(tafTapLocation.locationNames), - order: _calculateOrder(segmentIndex, timingPoint.location), + order: calculateOrder(segmentIndex, timingPoint.location), mandatoryStop: tpConstraint.stoppingPointInformation?.stopType?.mandatoryStop ?? true, isStop: tpConstraint.stopSkipPass == StopSkipPass.stoppingPoint, isStation: tafTapLocation.locationType != TafTapLocationType.stoppingLocation, bracketStation: _parseBracketStation(tafTapLocations, tafTapLocation), kilometre: kilometreMap[timingPoint.location] ?? [], - trackEquipment: trackEquipments.whereOnLocation(timingPoint.location).toList(), )); } @@ -107,16 +100,23 @@ class SferaModelMapper { } } + final trackEquipmentSegments = + TrackEquipmentMapper.parseNonStandardTrackEquipmentSegment(segmentProfilesLists, segmentProfiles); + journeyData.addAll(_cabSignalingStart(trackEquipmentSegments)); + journeyData.addAll(_cabSignalingEnd(trackEquipmentSegments)); + journeyData.sort((a, b) => a.order.compareTo(b.order)); final servicePoints = journeyData.where((it) => it.type == Datatype.servicePoint).toList(); return Journey( metadata: Metadata( - nextStop: servicePoints.length > 1 ? servicePoints[1] as ServicePoint : null, - currentPosition: journeyData.first, - additionalSpeedRestrictions: additionalSpeedRestrictions, - routeStart: journeyData.firstOrNull, - routeEnd: journeyData.lastOrNull), + nextStop: servicePoints.length > 1 ? servicePoints[1] as ServicePoint : null, + currentPosition: journeyData.first, + additionalSpeedRestrictions: additionalSpeedRestrictions, + routeStart: journeyData.firstOrNull, + routeEnd: journeyData.lastOrNull, + nonStandardTrackEquipmentSegment: trackEquipmentSegments, + ), data: journeyData, ); } @@ -161,14 +161,14 @@ class SferaModelMapper { } if (startSegmentIndex != null && endSegmentIndex != null && startLocation != null && endLocation != null) { - final startSegment = _findSegmentProfile(segmentProfiles, segmentProfilesLists[startSegmentIndex]); - final endSegment = _findSegmentProfile(segmentProfiles, segmentProfilesLists[endSegmentIndex]); + final startSegment = segmentProfiles.firstMatch(segmentProfilesLists[startSegmentIndex]); + final endSegment = segmentProfiles.firstMatch(segmentProfilesLists[endSegmentIndex]); - final startKilometreMap = _parseKilometre(startSegment); - final endKilometreMap = _parseKilometre(endSegment); + final startKilometreMap = parseKilometre(startSegment); + final endKilometreMap = parseKilometre(endSegment); - final startOrder = _calculateOrder(startSegmentIndex, startLocation); - final endOrder = _calculateOrder(endSegmentIndex, endLocation); + final startOrder = calculateOrder(startSegmentIndex, startLocation); + final endOrder = calculateOrder(endSegmentIndex, endLocation); result.add(AdditionalSpeedRestriction( kmFrom: startKilometreMap[startLocation]!.first, @@ -194,58 +194,20 @@ class SferaModelMapper { return result; } - static SegmentProfile _findSegmentProfile( - List segmentProfiles, SegmentProfileList segmentProfileList) { - return segmentProfiles - .where((it) => - it.id == segmentProfileList.spId && - it.versionMajor == segmentProfileList.versionMajor && - it.versionMinor == segmentProfileList.versionMinor) - .first; - } - - static Iterable _parseSignals(SegmentProfile segmentProfile, int segmentIndex, - Map> kilometreMap, List trackEquipments) { + static Iterable _parseSignals( + SegmentProfile segmentProfile, int segmentIndex, Map> kilometreMap) { final signals = segmentProfile.points?.signals ?? []; return signals.map((signal) { return Signal( visualIdentifier: signal.physicalCharacteristics?.visualIdentifier, functions: signal.functions.map((function) => SignalFunction.from(function.value!)).toList(), - order: _calculateOrder(segmentIndex, signal.id.location), + order: calculateOrder(segmentIndex, signal.id.location), kilometre: kilometreMap[signal.id.location] ?? [], - trackEquipment: trackEquipments.whereOnLocation(signal.id.location).toList(), ); }); } - static List _parseTrackEquipments(SegmentProfile segmentProfile) { - final nonStandardTrackEquipments = segmentProfile.areas?.nonStandardTrackEquipments ?? []; - return nonStandardTrackEquipments - .map((element) { - final trackEquipmentType = TrackEquipmentType.from(element.trackEquipmentType!.nspValue); - if (trackEquipmentType == null) { - Fimber.w( - 'Encountered nonStandardTrackEquipment without main station NSP declaration: ${element.trackEquipmentType}'); - return null; - } else { - final hasStartLocation = element.startEndQualifier == StartEndQualifier.starts || - element.startEndQualifier == StartEndQualifier.startsEnds; - final hasEndLocation = element.startEndQualifier == StartEndQualifier.ends || - element.startEndQualifier == StartEndQualifier.startsEnds; - return TrackEquipment( - type: trackEquipmentType, - startLocation: hasStartLocation ? element.startLocation! : null, - endLocation: hasEndLocation ? element.endLocation! : null, - appliesToWholeSp: element.startEndQualifier == StartEndQualifier.wholeSp, - ); - } - }) - .where((e) => e != null) - .cast() - .toList(); - } - - static int _calculateOrder(int segmentIndex, double location) { + static int calculateOrder(int segmentIndex, double location) { return (segmentIndex * _hundredThousand + location).toInt(); } @@ -257,7 +219,7 @@ class SferaModelMapper { ); } - static Map> _parseKilometre(SegmentProfile segmentProfile) { + static Map> parseKilometre(SegmentProfile segmentProfile) { final kilometreMap = >{}; if (segmentProfile.contextInformation != null) { for (final kilometreReferencePoint in segmentProfile.contextInformation!.kilometreReferencePoints) { @@ -293,7 +255,7 @@ class SferaModelMapper { journeyData.add(ProtectionSection( isOptional: isOptional != null ? bool.parse(isOptional.nspValue) : false, isLong: isLong != null ? XmlEnum.valueOf(LengthType.values, isLong.nspValue) == LengthType.long : false, - order: _calculateOrder(segmentIndex, currentLimitationChange.location), + order: calculateOrder(segmentIndex, currentLimitationChange.location), kilometre: kilometreMap[currentLimitationChange.location]!)); } } @@ -328,20 +290,29 @@ class SferaModelMapper { return null; } - static List _parseCurvePoints(SegmentProfile segmentProfile, int segmentIndex, - Map> kilometreMap, List trackEquipments) { + static List _parseCurvePoints( + SegmentProfile segmentProfile, int segmentIndex, Map> kilometreMap) { final curvePointsNsp = segmentProfile.points?.curvePointsNsp ?? []; return curvePointsNsp.map((curvePointNsp) { final curvePointTypeValue = curvePointNsp.parameters.withName('curvePointType')?.nspValue; final curveTypeValue = curvePointNsp.parameters.withName('curveType')?.nspValue; return CurvePoint( - order: _calculateOrder(segmentIndex, curvePointNsp.location), + order: calculateOrder(segmentIndex, curvePointNsp.location), kilometre: kilometreMap[curvePointNsp.location] ?? [], curvePointType: curvePointTypeValue != null ? CurvePointType.from(curvePointTypeValue) : null, curveType: curveTypeValue != null ? CurveType.from(curveTypeValue) : null, comment: curvePointNsp.parameters.withName('comment')?.nspValue, - trackEquipment: trackEquipments.whereOnLocation(curvePointNsp.location).toList(), ); }).toList(); } + + static Iterable _cabSignalingStart(List trackEquipmentSegments) { + return trackEquipmentSegments.withCABSignalingStart + .map((element) => CABSignaling(isStart: true, order: element.startOrder, kilometre: element.startKm)); + } + + static Iterable _cabSignalingEnd(List trackEquipmentSegments) { + return trackEquipmentSegments.withCABSignalingEnd + .map((element) => CABSignaling(isStart: false, order: element.endOrder, kilometre: element.endKm)); + } } diff --git a/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart b/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart new file mode 100644 index 00000000..83562793 --- /dev/null +++ b/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart @@ -0,0 +1,143 @@ +import 'package:das_client/model/journey/track_equipment.dart'; +import 'package:das_client/sfera/src/mapper/sfera_model_mapper.dart'; +import 'package:das_client/sfera/src/model/enums/start_end_qualifier.dart'; +import 'package:das_client/sfera/src/model/enums/track_equipment_type.dart'; +import 'package:das_client/sfera/src/model/network_specific_area.dart'; +import 'package:das_client/sfera/src/model/segment_profile.dart'; +import 'package:das_client/sfera/src/model/segment_profile_list.dart'; +import 'package:fimber/fimber.dart'; + +class TrackEquipmentMapper { + TrackEquipmentMapper._(); + + static List parseNonStandardTrackEquipmentSegment( + List segmentProfilesLists, List segmentProfiles) { + final trackEquipments = _parseTrackEquipments(segmentProfilesLists, segmentProfiles); + trackEquipments.sort((a, b) => a.compareTo(b)); + + final openStartSegments = {}; + + final List segments = []; + for (final trackEquipment in trackEquipments) { + if (trackEquipment.startLocation != null && trackEquipment.endLocation != null) { + segments.add(_createSegmentFromStartsEnds(trackEquipment)); + } else if (trackEquipment.startLocation != null) { + if (openStartSegments.containsKey(trackEquipment.type)) { + continue; + } + openStartSegments[trackEquipment.type] = trackEquipment; + } else if (trackEquipment.endLocation != null) { + final startOfSegment = openStartSegments[trackEquipment.type]; + if (startOfSegment != null) { + segments.add(_createSegment(startOfSegment, trackEquipment)); + + // Clear the pending start after creating a segment + openStartSegments[trackEquipment.type] = null; + } + } + } + + return segments; + } + + static NonStandardTrackEquipmentSegment _createSegmentFromStartsEnds(_NonStandardTrackEquipment trackEquipment) => + _createSegment(trackEquipment, trackEquipment); + + static NonStandardTrackEquipmentSegment _createSegment( + _NonStandardTrackEquipment start, _NonStandardTrackEquipment end) { + return NonStandardTrackEquipmentSegment( + type: start.type.toTrackEquipmentType(), + startOrder: SferaModelMapper.calculateOrder(start.index, start.startLocation!), + endOrder: SferaModelMapper.calculateOrder(end.index, end.endLocation!), + startKm: start.startKm, + endKm: end.endKm, + ); + } + + static List<_NonStandardTrackEquipment> _parseTrackEquipments( + List segmentProfilesLists, List segmentProfiles) { + final trackEquipments = <_NonStandardTrackEquipment>[]; + for (int segmentIndex = 0; segmentIndex < segmentProfilesLists.length; segmentIndex++) { + final segmentProfile = segmentProfiles.firstMatch(segmentProfilesLists[segmentIndex]); + final nonStandardTrackEquipments = segmentProfile.areas?.nonStandardTrackEquipments ?? []; + + final kilometreMap = SferaModelMapper.parseKilometre(segmentProfile); + + trackEquipments.addAll( + nonStandardTrackEquipments + .map((element) => _mapToNonStandardTrackEquipment(element, segmentIndex, kilometreMap)) + .where((e) => e != null) + .cast<_NonStandardTrackEquipment>() + .toList(), + ); + } + return trackEquipments; + } + + static _NonStandardTrackEquipment? _mapToNonStandardTrackEquipment(NetworkSpecificArea element, int segmentIndex, Map> kilometreMap) { + if (element.trackEquipmentTypeWrapper == null) { + Fimber.w('Encountered nonStandardTrackEquipment track equipment type NSP declaration: ${element.type}'); + return null; + } + + return _NonStandardTrackEquipment( + index: segmentIndex, + type: element.trackEquipmentTypeWrapper!.unwrapped, + startLocation: element.startLocation, + endLocation: element.endLocation, + appliesToWholeSp: element.startEndQualifier == StartEndQualifier.wholeSp, + startKm: kilometreMap[element.startLocation] ?? [], + endKm: kilometreMap[element.endLocation] ?? [], + ); + } +} + +/// data class used by mapper to be combined to NonStandardTrackEquipmentSegment +class _NonStandardTrackEquipment implements Comparable { + _NonStandardTrackEquipment({ + required this.startKm, + required this.endKm, + required this.type, + required this.index, + this.startLocation, + this.endLocation, + this.appliesToWholeSp = false, + }); + + final SferaTrackEquipmentType type; + final double? startLocation; + final double? endLocation; + final List startKm; + final List endKm; + final bool appliesToWholeSp; + final int index; + + @override + int compareTo(other) { + final indexComparison = index.compareTo(other.index); + if (indexComparison != 0) return indexComparison; + + // If indexes are equal, compare the startLocation + if (startLocation != null && other.startLocation != null) { + final startLocationComparison = startLocation!.compareTo(other.startLocation!); + if (startLocationComparison != 0) { + return startLocationComparison; + } + } else if (startLocation != null) { + return -1; + } else if (other.startLocation != null) { + return 1; + } + + // If both startLocation are equal or not provided, compare the endLocation + if (endLocation != null && other.endLocation != null) { + return endLocation!.compareTo(other.endLocation!); + } else if (endLocation != null) { + return -1; + } else if (other.endLocation != null) { + return 1; + } + + return 0; + } +} diff --git a/das_client/lib/sfera/src/model/enums/track_equipment_type.dart b/das_client/lib/sfera/src/model/enums/track_equipment_type.dart new file mode 100644 index 00000000..23e5fb72 --- /dev/null +++ b/das_client/lib/sfera/src/model/enums/track_equipment_type.dart @@ -0,0 +1,29 @@ +import 'package:das_client/model/journey/track_equipment.dart'; +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; + +enum SferaTrackEquipmentType implements XmlEnum { + etcsL1ls2TracksWithSingleTrackEquipment(xmlValue: 'ETCS-L1LS-2TracksWithSingleTrackEquipment'), + etcsL2ConvSpeedReversingImpossible(xmlValue: 'ETCS-L2-convSpeedReversingImpossible'), + etcsL2ExtSpeedReversingPossible(xmlValue: 'ETCS-L2-extSpeedReversingPossible'), + etcsL2ExtSpeedReversingImpossible(xmlValue: 'ETCS-L2-extSpeedReversingImpossible'); + + const SferaTrackEquipmentType({ + required this.xmlValue, + }); + + TrackEquipmentType toTrackEquipmentType() { + switch(this) { + case SferaTrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment: + return TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment; + case SferaTrackEquipmentType.etcsL2ConvSpeedReversingImpossible: + return TrackEquipmentType.etcsL2ConvSpeedReversingImpossible; + case SferaTrackEquipmentType.etcsL2ExtSpeedReversingPossible: + return TrackEquipmentType.etcsL2ExtSpeedReversingPossible; + case SferaTrackEquipmentType.etcsL2ExtSpeedReversingImpossible: + return TrackEquipmentType.etcsL2ExtSpeedReversingImpossible; + } + } + + @override + final String xmlValue; +} diff --git a/das_client/lib/sfera/src/model/network_specific_area.dart b/das_client/lib/sfera/src/model/network_specific_area.dart index 62dfd3df..f042c64e 100644 --- a/das_client/lib/sfera/src/model/network_specific_area.dart +++ b/das_client/lib/sfera/src/model/network_specific_area.dart @@ -2,10 +2,10 @@ import 'package:das_client/sfera/src/model/enums/start_end_qualifier.dart'; import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/track_equipment_type_wrapper.dart'; class NetworkSpecificArea extends SferaXmlElement { static const String elementType = 'NetworkSpecificArea'; - static const String _trackEquipmentTypeName = 'trackEquipmentType'; NetworkSpecificArea({super.type = elementType, super.attributes, super.children, super.value}); @@ -20,8 +20,7 @@ class NetworkSpecificArea extends SferaXmlElement { Iterable get networkSpecificParameters => children.whereType(); - NetworkSpecificParameter? get trackEquipmentType => - children.whereType().where((it) => it.name == _trackEquipmentTypeName).firstOrNull; + TrackEquipmentTypeWrapper? get trackEquipmentTypeWrapper => children.whereType().firstOrNull; double? _parseOrNull(String? source) { return source != null ? double.parse(source) : null; diff --git a/das_client/lib/sfera/src/model/network_specific_parameter.dart b/das_client/lib/sfera/src/model/network_specific_parameter.dart index 305f7af1..0fb66516 100644 --- a/das_client/lib/sfera/src/model/network_specific_parameter.dart +++ b/das_client/lib/sfera/src/model/network_specific_parameter.dart @@ -1,10 +1,19 @@ import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/track_equipment_type_wrapper.dart'; class NetworkSpecificParameter extends SferaXmlElement { static const String elementType = 'NetworkSpecificParameter'; NetworkSpecificParameter({super.type = elementType, super.attributes, super.children, super.value}); + factory NetworkSpecificParameter.from( + {Map? attributes, List? children, String? value}) { + if (attributes?['name'] == TrackEquipmentTypeWrapper.elementName) { + return TrackEquipmentTypeWrapper(attributes: attributes, children: children, value: value); + } + return NetworkSpecificParameter(attributes: attributes, children: children, value: value); + } + String get name => attributes['name']!; String get nspValue => attributes['value']!; diff --git a/das_client/lib/sfera/src/model/segment_profile.dart b/das_client/lib/sfera/src/model/segment_profile.dart index f30d4894..d1f66e6a 100644 --- a/das_client/lib/sfera/src/model/segment_profile.dart +++ b/das_client/lib/sfera/src/model/segment_profile.dart @@ -1,5 +1,6 @@ import 'package:das_client/sfera/src/model/enums/sp_status.dart'; import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; +import 'package:das_client/sfera/src/model/segment_profile_list.dart'; import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; import 'package:das_client/sfera/src/model/sp_areas.dart'; import 'package:das_client/sfera/src/model/sp_characteristics.dart'; @@ -41,3 +42,14 @@ class SegmentProfile extends SferaXmlElement { super.validate(); } } + +// extensions + +extension SegmentProfileListExtension on Iterable { + SegmentProfile firstMatch(SegmentProfileList segmentProfileList) { + return where((it) => + it.id == segmentProfileList.spId && + it.versionMajor == segmentProfileList.versionMajor && + it.versionMinor == segmentProfileList.versionMinor).first; + } +} diff --git a/das_client/lib/sfera/src/model/track_equipment_type_wrapper.dart b/das_client/lib/sfera/src/model/track_equipment_type_wrapper.dart new file mode 100644 index 00000000..b76f4647 --- /dev/null +++ b/das_client/lib/sfera/src/model/track_equipment_type_wrapper.dart @@ -0,0 +1,16 @@ +import 'package:das_client/sfera/src/model/enums/track_equipment_type.dart'; +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; +import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; + +class TrackEquipmentTypeWrapper extends NetworkSpecificParameter { + static const String elementName = 'trackEquipmentType'; + + TrackEquipmentTypeWrapper({super.type, super.attributes, super.children, super.value}); + + SferaTrackEquipmentType get unwrapped => XmlEnum.valueOf(SferaTrackEquipmentType.values, attributes['value']!)!; + + @override + bool validate() { + return validateHasEnumAttribute(SferaTrackEquipmentType.values, 'value') && super.validate(); + } +} \ No newline at end of file diff --git a/das_client/lib/sfera/src/service/sfera_service.dart b/das_client/lib/sfera/src/service/sfera_service.dart index 9741db50..dc36d66e 100644 --- a/das_client/lib/sfera/src/service/sfera_service.dart +++ b/das_client/lib/sfera/src/service/sfera_service.dart @@ -22,7 +22,7 @@ abstract class SferaService { void disconnect(); - static Future messageHeader({TrainIdentification? trainIdentification, required String sender}) async { + static Future messageHeader({required String sender, TrainIdentification? trainIdentification}) async { return MessageHeader.create(const Uuid().v4(), Format.sferaTimestamp(DateTime.now()), await DeviceIdInfo.getDeviceId(), 'TMS', sender, '0085', trainIdentification: trainIdentification); diff --git a/das_client/lib/sfera/src/sfera_reply_parser.dart b/das_client/lib/sfera/src/sfera_reply_parser.dart index 8fdbdb00..d7f790ca 100644 --- a/das_client/lib/sfera/src/sfera_reply_parser.dart +++ b/das_client/lib/sfera/src/sfera_reply_parser.dart @@ -154,7 +154,7 @@ class SferaReplyParser { case TafTapLocationNsp.elementType: return TafTapLocationNsp(type: type, attributes: attributes, children: children, value: value); case NetworkSpecificParameter.elementType: - return NetworkSpecificParameter(type: type, attributes: attributes, children: children, value: value); + return NetworkSpecificParameter.from(attributes: attributes, children: children, value: value); case CurrentLimitation.elementType: return CurrentLimitation(type: type, attributes: attributes, children: children, value: value); case CurrentLimitationStart.elementType: diff --git a/das_client/test/model/journey/track_equipment_test.dart b/das_client/test/model/journey/track_equipment_test.dart index 799402ce..c7bb1277 100644 --- a/das_client/test/model/journey/track_equipment_test.dart +++ b/das_client/test/model/journey/track_equipment_test.dart @@ -3,21 +3,16 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('Test track equipment', () { - test('Test track equipment on location with start and end location', () { + test('Test track equipment applies to order', () { // given - final trackEquipment = TrackEquipment( - type: TrackEquipmentType.etcsL2ExtSpeedReversingPossible, - startLocation: 100.0, - endLocation: 300.0, - appliesToWholeSp: false, - ); + final trackEquipment = _etcsL2ExtSpeedReversingPossible(100, 300); // when - final belowStart = trackEquipment.isOnLocation(50.0); - final onStart = trackEquipment.isOnLocation(100.0); - final between = trackEquipment.isOnLocation(200.0); - final onEnd = trackEquipment.isOnLocation(300.0); - final aboveEnd = trackEquipment.isOnLocation(400.0); + final belowStart = trackEquipment.appliesToOrder(50); + final onStart = trackEquipment.appliesToOrder(100); + final between = trackEquipment.appliesToOrder(200); + final onEnd = trackEquipment.appliesToOrder(300); + final aboveEnd = trackEquipment.appliesToOrder(400); // then expect(belowStart, isFalse); @@ -27,82 +22,159 @@ void main() { expect(aboveEnd, isFalse); }); - test('Test track equipment on location that applies whole segment', () async { - final trackEquipment = TrackEquipment( - type: TrackEquipmentType.etcsL2ExtSpeedReversingPossible, - appliesToWholeSp: true, - ); + test('Test CAB signaling start and end for connected segment', () { + // given + final trackEquipment1 = _etcsL2ExtSpeedReversingPossible(100, 300); + final trackEquipment2 = _etcsL2ExtSpeedReversingImpossible(300, 500); + final trackEquipment3 = _etcsL2ConvSpeedReversingImpossible(500, 800); + final trackEquipment4 = _etcsL2ExtSpeedReversingImpossible(800, 900); + final trackEquipment5 = _etcsL2ExtSpeedReversingPossible(900, 1000); - // this combination would normally not happen but should be tested. - final trackEquipmentWithStartEnd = TrackEquipment( - type: TrackEquipmentType.etcsL2ExtSpeedReversingPossible, - startLocation: 100.0, - endLocation: 300.0, - appliesToWholeSp: true, - ); + final trackEquipments = [ + trackEquipment1, + trackEquipment2, + trackEquipment3, + trackEquipment4, + trackEquipment5, + ]; // when - final onLocation1 = trackEquipment.isOnLocation(0); - final onLocation2 = trackEquipment.isOnLocation(9999); - final belowStart = trackEquipmentWithStartEnd.isOnLocation(50.0); - final onStart = trackEquipmentWithStartEnd.isOnLocation(100.0); - final between = trackEquipmentWithStartEnd.isOnLocation(200.0); - final onEnd = trackEquipmentWithStartEnd.isOnLocation(300.0); - final aboveEnd = trackEquipmentWithStartEnd.isOnLocation(400.0); + final withCABSignalingStart = trackEquipments.withCABSignalingStart; + final withCABSignalingEnd = trackEquipments.withCABSignalingEnd; // then - expect(onLocation1, isTrue); - expect(onLocation2, isTrue); - expect(belowStart, isTrue); - expect(onStart, isTrue); - expect(between, isTrue); - expect(onEnd, isTrue); - expect(aboveEnd, isTrue); + expect(withCABSignalingStart, hasLength(1)); + expect(withCABSignalingStart.first, trackEquipment1); + expect(withCABSignalingEnd, hasLength(1)); + expect(withCABSignalingEnd.first, trackEquipment5); }); - test('Test track equipment on location with only start or end location', () async { - final trackEquipmentWithStart = TrackEquipment( - type: TrackEquipmentType.etcsL2ExtSpeedReversingPossible, - startLocation: 300.0, - ); + test('Test CAB signaling start and end for separated segments', () { + // given + final trackEquipment1Segment1 = _etcsL2ExtSpeedReversingPossible(200, 300); + final trackEquipment2Segment1 = _etcsL2ExtSpeedReversingImpossible(300, 600); - final trackEquipmentWithEnd = TrackEquipment( - type: TrackEquipmentType.etcsL2ExtSpeedReversingPossible, - endLocation: 400.0, - ); + final trackEquipment1Segment2 = _etcsL2ConvSpeedReversingImpossible(800, 1000); + final trackEquipment2Segment2 = _etcsL2ExtSpeedReversingImpossible(1000, 1200); + final trackEquipment3Segment2 = _etcsL2ExtSpeedReversingPossible(1200, 1500); + + final trackEquipments = [ + trackEquipment1Segment1, + trackEquipment2Segment1, + trackEquipment1Segment2, + trackEquipment2Segment2, + trackEquipment3Segment2, + ]; // when - final belowStart = trackEquipmentWithStart.isOnLocation(100); - final onStart = trackEquipmentWithStart.isOnLocation(300); - final aboveStart = trackEquipmentWithStart.isOnLocation(400); - final belowEnd = trackEquipmentWithEnd.isOnLocation(100); - final onEnd = trackEquipmentWithEnd.isOnLocation(400); - final aboveEnd = trackEquipmentWithEnd.isOnLocation(600); + final withCABSignalingStart = trackEquipments.withCABSignalingStart; + final withCABSignalingEnd = trackEquipments.withCABSignalingEnd; // then - expect(belowStart, isFalse); - expect(onStart, isTrue); - expect(aboveStart, isTrue); - expect(belowEnd, isTrue); - expect(onEnd, isTrue); - expect(aboveEnd, isFalse); + expect(withCABSignalingStart, hasLength(2)); + expect(withCABSignalingStart[0], trackEquipment1Segment1); + expect(withCABSignalingStart[1], trackEquipment1Segment2); + expect(withCABSignalingEnd, hasLength(2)); + expect(withCABSignalingEnd[0], trackEquipment2Segment1); + expect(withCABSignalingEnd[1], trackEquipment3Segment2); }); - }); + test('Test CAB signaling start and end with single track equipments and a L1LS in between', () { + // given + final trackEquipment1 = _etcsL2ExtSpeedReversingPossible(100, 300); + final trackEquipment2 = _etcsL1ls2TracksWithSingleTrackEquipment(300, 500); + final trackEquipment3 = _etcsL2ConvSpeedReversingImpossible(500, 800); + + final trackEquipments = [ + trackEquipment1, + trackEquipment2, + trackEquipment3, + ]; + + // when + final withCABSignalingStart = trackEquipments.withCABSignalingStart; + final withCABSignalingEnd = trackEquipments.withCABSignalingEnd; + + // then + expect(withCABSignalingStart, hasLength(2)); + expect(withCABSignalingStart[0], trackEquipment1); + expect(withCABSignalingStart[1], trackEquipment3); + expect(withCABSignalingEnd, hasLength(2)); + expect(withCABSignalingEnd[0], trackEquipment1); + expect(withCABSignalingEnd[1], trackEquipment3); + }); + + test('Test CAB signaling start and end with single track equipment', () { + // given + final trackEquipment1 = _etcsL2ExtSpeedReversingPossible(100, 300); + + final trackEquipments = [trackEquipment1]; + + // when + final withCABSignalingStart = trackEquipments.withCABSignalingStart; + final withCABSignalingEnd = trackEquipments.withCABSignalingEnd; + + // then + expect(withCABSignalingStart, hasLength(1)); + expect(withCABSignalingStart[0], trackEquipment1); + expect(withCABSignalingEnd, hasLength(1)); + expect(withCABSignalingEnd[0], trackEquipment1); + }); + }); group('Test track equipment type', () { - test('Test string factory for track equipment type', () { + test('Test if type is ETCS level 2', () { // when - final tracksWithSingleTrackEquipment = TrackEquipmentType.from('ETCS-L1LS-2TracksWithSingleTrackEquipment'); - final convSpeedReversingImpossible = TrackEquipmentType.from('ETCS-L2-convSpeedReversingImpossible'); - final extSpeedReversingPossible = TrackEquipmentType.from('ETCS-L2-extSpeedReversingPossible'); - final extSpeedReversingImpossible = TrackEquipmentType.from('ETCS-L2-extSpeedReversingImpossible'); + final tracksWithSingleTrackEquipment = TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment.isEtcsL2(); + final convSpeedReversingImpossible = TrackEquipmentType.etcsL2ConvSpeedReversingImpossible.isEtcsL2(); + final extSpeedReversingPossible = TrackEquipmentType.etcsL2ExtSpeedReversingPossible.isEtcsL2(); + final extSpeedReversingImpossible = TrackEquipmentType.etcsL2ConvSpeedReversingImpossible.isEtcsL2(); // then - expect(tracksWithSingleTrackEquipment, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); - expect(convSpeedReversingImpossible, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); - expect(extSpeedReversingPossible, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); - expect(extSpeedReversingImpossible, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); + expect(tracksWithSingleTrackEquipment, isFalse); + expect(convSpeedReversingImpossible, isTrue); + expect(extSpeedReversingPossible, isTrue); + expect(extSpeedReversingImpossible, isTrue); }); }); } + +NonStandardTrackEquipmentSegment _etcsL2ExtSpeedReversingPossible(int startOrder, int endOrder) { + return NonStandardTrackEquipmentSegment( + type: TrackEquipmentType.etcsL2ExtSpeedReversingPossible, + startOrder: startOrder, + endOrder: endOrder, + startKm: [], + endKm: [], + ); +} + +NonStandardTrackEquipmentSegment _etcsL2ExtSpeedReversingImpossible(int startOrder, int endOrder) { + return NonStandardTrackEquipmentSegment( + type: TrackEquipmentType.etcsL2ExtSpeedReversingImpossible, + startOrder: startOrder, + endOrder: endOrder, + startKm: [], + endKm: [], + ); +} + +NonStandardTrackEquipmentSegment _etcsL2ConvSpeedReversingImpossible(int startOrder, int endOrder) { + return NonStandardTrackEquipmentSegment( + type: TrackEquipmentType.etcsL2ConvSpeedReversingImpossible, + startOrder: startOrder, + endOrder: endOrder, + startKm: [], + endKm: [], + ); +} + +NonStandardTrackEquipmentSegment _etcsL1ls2TracksWithSingleTrackEquipment(int startOrder, int endOrder) { + return NonStandardTrackEquipmentSegment( + type: TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment, + startOrder: startOrder, + endOrder: endOrder, + startKm: [], + endKm: [], + ); +} diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart index a7d0d5ff..3b1828e9 100644 --- a/das_client/test/sfera/mapper/sfera_mapper_test.dart +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:das_client/model/journey/additional_speed_restriction_data.dart'; +import 'package:das_client/model/journey/cab_signaling.dart'; import 'package:das_client/model/journey/curve_point.dart'; import 'package:das_client/model/journey/datatype.dart'; import 'package:das_client/model/journey/journey.dart'; @@ -53,19 +54,20 @@ void main() { final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); expect(journey.valid, true); - expect(servicePoints, hasLength(5)); + expect(servicePoints, hasLength(6)); expect(servicePoints[0].name.de, 'Bahnhof A'); expect(servicePoints[1].name.de, 'Haltestelle B'); expect(servicePoints[2].name.de, 'Halt auf Verlangen C'); expect(servicePoints[3].name.de, 'Klammerbahnhof D'); expect(servicePoints[4].name.de, 'Klammerbahnhof D1'); + expect(servicePoints[5].name.de, 'Bahnhof E'); }); test('Test journey data types correctly generated', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(16)); + expect(journey.data, hasLength(21)); // segment 1 expect(journey.data[0], TypeMatcher()); @@ -73,28 +75,33 @@ void main() { expect(journey.data[2], TypeMatcher()); expect(journey.data[3], TypeMatcher()); // segment 2 - expect(journey.data[4], TypeMatcher()); - expect(journey.data[5], TypeMatcher()); - expect(journey.data[6], TypeMatcher()); - expect(journey.data[7], TypeMatcher()); - // segment 3 + expect(journey.data[4], TypeMatcher()); + expect(journey.data[5], TypeMatcher()); + expect(journey.data[6], TypeMatcher()); + expect(journey.data[7], TypeMatcher()); expect(journey.data[8], TypeMatcher()); - expect(journey.data[9], TypeMatcher()); - expect(journey.data[10], TypeMatcher()); - expect(journey.data[11], TypeMatcher()); + // segment 3 + expect(journey.data[9], TypeMatcher()); + expect(journey.data[10], TypeMatcher()); + expect(journey.data[11], TypeMatcher()); + expect(journey.data[12], TypeMatcher()); // segment 4 - expect(journey.data[12], TypeMatcher()); - expect(journey.data[13], TypeMatcher()); + expect(journey.data[13], TypeMatcher()); + expect(journey.data[14], TypeMatcher()); + expect(journey.data[15], TypeMatcher()); // segment 5 - expect(journey.data[14], TypeMatcher()); - expect(journey.data[15], TypeMatcher()); + expect(journey.data[16], TypeMatcher()); + expect(journey.data[17], TypeMatcher()); + expect(journey.data[18], TypeMatcher()); + expect(journey.data[19], TypeMatcher()); + expect(journey.data[20], TypeMatcher()); }); test('Test kilometre are parsed correctly', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(16)); + expect(journey.data, hasLength(21)); // segment 1 expect(journey.data[0].kilometre[0], 0.2); @@ -102,29 +109,34 @@ void main() { expect(journey.data[2].kilometre[0], 0.6); expect(journey.data[3].kilometre[0], 0.7); // segment 2 - expect(journey.data[4].kilometre[0], 1.2); - expect(journey.data[5].kilometre[0], 1.5); - expect(journey.data[6].kilometre[0], 1.7); - expect(journey.data[7].kilometre[0], 1.8); + expect(journey.data[4].kilometre[0], 1.1); + expect(journey.data[5].kilometre[0], 1.2); + expect(journey.data[6].kilometre[0], 1.5); + expect(journey.data[7].kilometre[0], 1.7); + expect(journey.data[8].kilometre[0], 1.8); // segment 3 - expect(journey.data[8].kilometre[0], 2.1); - expect(journey.data[9].kilometre[0], 2.4); - expect(journey.data[10].kilometre[0], 2.5); - expect(journey.data[11].kilometre[0], 2.6); + expect(journey.data[9].kilometre[0], 2.1); + expect(journey.data[10].kilometre[0], 2.4); + expect(journey.data[11].kilometre[0], 2.5); + expect(journey.data[12].kilometre[0], 2.6); // segment 4 - expect(journey.data[12].kilometre[0], 3.7); - expect(journey.data[12].kilometre[1], 0); - expect(journey.data[13].kilometre[0], 0.2); + expect(journey.data[13].kilometre[0], 3.5); + expect(journey.data[14].kilometre[0], 3.7); + expect(journey.data[14].kilometre[1], 0); + expect(journey.data[15].kilometre[0], 0.2); // segment 5 - expect(journey.data[14].kilometre[0], 0.4); - expect(journey.data[15].kilometre[0], 0.6); + expect(journey.data[16].kilometre[0], 0.6); + expect(journey.data[17].kilometre[0], 0.6); + expect(journey.data[18].kilometre[0], 0.9); + expect(journey.data[19].kilometre[0], 1.0); + expect(journey.data[20].kilometre[0], 1.1); }); test('Test order is generated correctly', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(16)); + expect(journey.data, hasLength(21)); // segment 1 expect(journey.data[0].order, 000200); @@ -132,73 +144,46 @@ void main() { expect(journey.data[2].order, 000600); expect(journey.data[3].order, 000700); // segment 2 - expect(journey.data[4].order, 100200); - expect(journey.data[5].order, 100500); - expect(journey.data[6].order, 100700); - expect(journey.data[7].order, 100800); + expect(journey.data[4].order, 100100); + expect(journey.data[5].order, 100200); + expect(journey.data[6].order, 100500); + expect(journey.data[7].order, 100700); + expect(journey.data[8].order, 100800); // segment 3 - expect(journey.data[8].order, 200100); - expect(journey.data[9].order, 200400); - expect(journey.data[10].order, 200500); - expect(journey.data[11].order, 200600); + expect(journey.data[9].order, 200100); + expect(journey.data[10].order, 200400); + expect(journey.data[11].order, 200500); + expect(journey.data[12].order, 200600); // segment 4 - expect(journey.data[12].order, 300700); - expect(journey.data[13].order, 300900); + expect(journey.data[13].order, 300500); + expect(journey.data[14].order, 300700); + expect(journey.data[15].order, 300900); // segment 5 - expect(journey.data[14].order, 400100); - expect(journey.data[15].order, 400300); + expect(journey.data[16].order, 400300); + expect(journey.data[17].order, 400300); + expect(journey.data[18].order, 400600); + expect(journey.data[19].order, 400700); + expect(journey.data[20].order, 400800); }); test('Test track equipment is generated correctly', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(16)); - - // segment 1 - expect(journey.data[0].trackEquipment, isEmpty); - expect(journey.data[1].trackEquipment, isEmpty); - expect(journey.data[2].trackEquipment, isEmpty); - expect(journey.data[3].trackEquipment, isEmpty); - // segment 2 - expect(journey.data[4].trackEquipment, hasLength(1)); - expect(journey.data[4].trackEquipment[0].appliesToWholeSp, isTrue); - expect(journey.data[4].trackEquipment[0].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); - expect(journey.data[5].trackEquipment, hasLength(1)); - expect(journey.data[5].trackEquipment[0].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); - expect(journey.data[6].trackEquipment, hasLength(1)); - expect(journey.data[6].trackEquipment[0].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); - expect(journey.data[7].trackEquipment, hasLength(1)); - expect(journey.data[7].trackEquipment[0].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); - // segment 3 - expect(journey.data[8].trackEquipment, hasLength(1)); - expect(journey.data[8].trackEquipment[0].appliesToWholeSp, isFalse); - expect(journey.data[8].trackEquipment[0].startLocation, 100.0); - expect(journey.data[8].trackEquipment[0].endLocation, 400.0); - expect(journey.data[8].trackEquipment[0].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); - expect(journey.data[9].trackEquipment, hasLength(1)); - expect(journey.data[9].trackEquipment[0].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); - expect(journey.data[10].trackEquipment, hasLength(1)); - expect(journey.data[10].trackEquipment[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); - expect(journey.data[10].trackEquipment[0].appliesToWholeSp, isFalse); - expect(journey.data[10].trackEquipment[0].startLocation, 500.0); - expect(journey.data[10].trackEquipment[0].endLocation, isNull); - expect(journey.data[11].trackEquipment, hasLength(1)); - expect(journey.data[11].trackEquipment[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); - // segment 4 - expect(journey.data[12].trackEquipment, hasLength(1)); - expect(journey.data[12].trackEquipment[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); - expect(journey.data[12].trackEquipment[0].appliesToWholeSp, isFalse); - expect(journey.data[12].trackEquipment[0].startLocation, isNull); - expect(journey.data[12].trackEquipment[0].endLocation, 800.0); - expect(journey.data[13].trackEquipment, isEmpty); - // segment 5 - expect(journey.data[14].trackEquipment, isEmpty); - expect(journey.data[15].trackEquipment, hasLength(1)); - expect(journey.data[15].trackEquipment[0].startLocation, 300.0); - expect(journey.data[15].trackEquipment[0].endLocation, 800.0); - expect(journey.data[15].trackEquipment[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); - expect(journey.data[15].trackEquipment[0].appliesToWholeSp, isFalse); + expect(journey.metadata.nonStandardTrackEquipmentSegment, hasLength(4)); + + expect(journey.metadata.nonStandardTrackEquipmentSegment[0].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); + expect(journey.metadata.nonStandardTrackEquipmentSegment[0].startOrder, 000600); + expect(journey.metadata.nonStandardTrackEquipmentSegment[0].endOrder, 000900); + expect(journey.metadata.nonStandardTrackEquipmentSegment[1].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegment[1].startOrder, 100100); + expect(journey.metadata.nonStandardTrackEquipmentSegment[1].endOrder, 100700); + expect(journey.metadata.nonStandardTrackEquipmentSegment[2].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect(journey.metadata.nonStandardTrackEquipmentSegment[2].startOrder, 100700); + expect(journey.metadata.nonStandardTrackEquipmentSegment[2].endOrder, 300500); + expect(journey.metadata.nonStandardTrackEquipmentSegment[3].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegment[3].startOrder, 400300); + expect(journey.metadata.nonStandardTrackEquipmentSegment[3].endOrder, 400700); }); test('Test signals are generated correctly', () async { @@ -258,12 +243,13 @@ void main() { final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); expect(journey.valid, true); - expect(servicePoints, hasLength(5)); + expect(servicePoints, hasLength(6)); expect(servicePoints[0].mandatoryStop, true); expect(servicePoints[1].mandatoryStop, true); expect(servicePoints[2].mandatoryStop, false); expect(servicePoints[3].mandatoryStop, true); expect(servicePoints[4].mandatoryStop, true); + expect(servicePoints[5].mandatoryStop, true); }); test('Test passing point is parsed correctly', () async { @@ -271,12 +257,13 @@ void main() { final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); expect(journey.valid, true); - expect(servicePoints, hasLength(5)); + expect(servicePoints, hasLength(6)); expect(servicePoints[0].isStop, true); expect(servicePoints[1].isStop, false); expect(servicePoints[2].isStop, true); expect(servicePoints[3].isStop, true); expect(servicePoints[4].isStop, true); + expect(servicePoints[5].isStop, true); }); test('Test station point is parsed correctly', () async { @@ -284,12 +271,13 @@ void main() { final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); expect(journey.valid, true); - expect(servicePoints, hasLength(5)); + expect(servicePoints, hasLength(6)); expect(servicePoints[0].isStation, true); expect(servicePoints[1].isStation, true); expect(servicePoints[2].isStation, false); expect(servicePoints[3].isStation, true); expect(servicePoints[4].isStation, true); + expect(servicePoints[5].isStation, true); }); test('Test bracket stations is parsed correctly', () async { @@ -297,7 +285,7 @@ void main() { final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); expect(journey.valid, true); - expect(servicePoints, hasLength(5)); + expect(servicePoints, hasLength(6)); expect(servicePoints[0].bracketStation, isNull); expect(servicePoints[1].bracketStation, isNull); expect(servicePoints[2].bracketStation, isNull); @@ -305,6 +293,7 @@ void main() { expect(servicePoints[3].bracketStation!.mainStationAbbreviation, isNull); expect(servicePoints[4].bracketStation, isNotNull); expect(servicePoints[4].bracketStation!.mainStationAbbreviation, 'D'); + expect(servicePoints[5].bracketStation, isNull); }); test('Test protection section is parsed correctly', () async { diff --git a/das_client/test_resources/jp/SFERA_JP_9999.xml b/das_client/test_resources/jp/SFERA_JP_9999.xml index f9ab6e13..591be207 100644 --- a/das_client/test_resources/jp/SFERA_JP_9999.xml +++ b/das_client/test_resources/jp/SFERA_JP_9999.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../SFERA_3.0_custom.xsd"> 1085 @@ -56,10 +56,15 @@ 0085 - + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_1.xml b/das_client/test_resources/sp/SFERA_SP_9999_1.xml index fd52d677..9fe83e57 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_1.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_1.xml @@ -49,27 +49,32 @@ - + - + - + - + + + + + + @@ -82,6 +87,9 @@ + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_2.xml b/das_client/test_resources/sp/SFERA_SP_9999_2.xml index 418f201d..923dcc78 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_2.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_2.xml @@ -37,7 +37,7 @@ + TAF_TAP_location_type="station"> CH 9992 @@ -45,13 +45,20 @@ - - + + + + + + + + + @@ -64,5 +71,8 @@ + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_3.xml b/das_client/test_resources/sp/SFERA_SP_9999_3.xml index 75f5b546..5b25ddd9 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_3.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_3.xml @@ -34,6 +34,17 @@ + + + + @@ -46,18 +57,19 @@ - - - - + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_4.xml b/das_client/test_resources/sp/SFERA_SP_9999_4.xml index 1e127c46..f5fa2e6b 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_4.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_4.xml @@ -20,6 +20,26 @@ + + + + + + @@ -34,23 +54,27 @@ - + - + + + + + + - diff --git a/das_client/test_resources/sp/SFERA_SP_9999_5.xml b/das_client/test_resources/sp/SFERA_SP_9999_5.xml index 6614ffc4..56079c24 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_5.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_5.xml @@ -14,8 +14,15 @@ 9995 + + + + CH + 9996 + + - + block intermediate @@ -24,7 +31,7 @@ + TAF_TAP_location_type="station"> CH 9995 @@ -34,18 +41,32 @@ - + + + CH + 9996 + + + + + - - - - + + + + + + + + + From f4dc23a7ce035cecbab5ab9c2e525a4ce3d203e5 Mon Sep 17 00:00:00 2001 From: u221638 Date: Fri, 6 Dec 2024 13:16:48 +0100 Subject: [PATCH 02/22] feat: handled track equipment segments that start or end outside of train journey (#82). --- .../journey/additional_speed_restriction.dart | 5 + .../additional_speed_restriction_data.dart | 5 + das_client/lib/model/journey/base_data.dart | 8 +- .../lib/model/journey/bracket_station.dart | 5 + .../lib/model/journey/cab_signaling.dart | 15 ++ das_client/lib/model/journey/curve_point.dart | 5 + .../lib/model/journey/protection_section.dart | 5 + .../lib/model/journey/service_point.dart | 5 + das_client/lib/model/journey/signal.dart | 5 + .../lib/model/journey/track_equipment.dart | 82 ++++++--- das_client/lib/model/localized_string.dart | 5 + .../sfera/src/mapper/sfera_model_mapper.dart | 10 +- .../src/mapper/track_equipment_mapper.dart | 90 ++++++---- .../src/model/enums/track_equipment_type.dart | 36 ++-- das_client/lib/util/comparators.dart | 32 ++++ .../model/journey/track_equipment_test.dart | 89 ++++++++-- .../test/sfera/mapper/sfera_mapper_test.dart | 168 ++++++++++-------- .../test_resources/sp/SFERA_SP_9999_1.xml | 7 + .../test_resources/sp/SFERA_SP_9999_2.xml | 2 +- .../test_resources/sp/SFERA_SP_9999_5.xml | 6 +- 20 files changed, 408 insertions(+), 177 deletions(-) create mode 100644 das_client/lib/util/comparators.dart diff --git a/das_client/lib/model/journey/additional_speed_restriction.dart b/das_client/lib/model/journey/additional_speed_restriction.dart index f11d6938..cff0a5b7 100644 --- a/das_client/lib/model/journey/additional_speed_restriction.dart +++ b/das_client/lib/model/journey/additional_speed_restriction.dart @@ -13,4 +13,9 @@ class AdditionalSpeedRestriction { bool needsEndMarker(List journeyData) { return journeyData.where((it) => it.order >= orderFrom && it.order <= orderTo).length > 1; } + + @override + String toString() { + return 'AdditionalSpeedRestriction(kmFrom: $kmFrom, kmTo: $kmTo, orderFrom: $orderFrom, orderTo: $orderTo, speed: $speed)'; + } } diff --git a/das_client/lib/model/journey/additional_speed_restriction_data.dart b/das_client/lib/model/journey/additional_speed_restriction_data.dart index 3995aa2e..e047600e 100644 --- a/das_client/lib/model/journey/additional_speed_restriction_data.dart +++ b/das_client/lib/model/journey/additional_speed_restriction_data.dart @@ -7,4 +7,9 @@ class AdditionalSpeedRestrictionData extends BaseData { : super(type: Datatype.additionalSpeedRestriction); final AdditionalSpeedRestriction restriction; + + @override + String toString() { + return 'AdditionalSpeedRestrictionData(order: $order, kilometre: $kilometre, restriction: $AdditionalSpeedRestriction)'; + } } diff --git a/das_client/lib/model/journey/base_data.dart b/das_client/lib/model/journey/base_data.dart index 1aefcb66..47639b46 100644 --- a/das_client/lib/model/journey/base_data.dart +++ b/das_client/lib/model/journey/base_data.dart @@ -1,6 +1,6 @@ import 'package:das_client/model/journey/datatype.dart'; -abstract class BaseData { +abstract class BaseData implements Comparable { BaseData({ required this.type, required this.order, @@ -10,4 +10,10 @@ abstract class BaseData { final Datatype type; final int order; final List kilometre; + + @override + int compareTo(other) { + if(other is! BaseData) return -1; + return order.compareTo(other.order); + } } diff --git a/das_client/lib/model/journey/bracket_station.dart b/das_client/lib/model/journey/bracket_station.dart index 96268be8..94dd5896 100644 --- a/das_client/lib/model/journey/bracket_station.dart +++ b/das_client/lib/model/journey/bracket_station.dart @@ -2,4 +2,9 @@ class BracketStation { BracketStation({this.mainStationAbbreviation}); final String? mainStationAbbreviation; + + @override + String toString() { + return "BracketStation(mainStationAbbreviation: '$mainStationAbbreviation')"; + } } diff --git a/das_client/lib/model/journey/cab_signaling.dart b/das_client/lib/model/journey/cab_signaling.dart index 6a9a6cd2..08d51fed 100644 --- a/das_client/lib/model/journey/cab_signaling.dart +++ b/das_client/lib/model/journey/cab_signaling.dart @@ -9,4 +9,19 @@ class CABSignaling extends BaseData { }) : super(type: Datatype.cabSignaling); final bool isStart; + + @override + int compareTo(other) { + final comparison = super.compareTo(other); + if (comparison != 0) { + return comparison; + } + + return isStart ? -1 : 1; + } + + @override + String toString() { + return 'CABSignaling(order: $order, kilometre: $kilometre, isStart: $isStart)'; + } } \ No newline at end of file diff --git a/das_client/lib/model/journey/curve_point.dart b/das_client/lib/model/journey/curve_point.dart index 4f9822ed..aa6d9b0f 100644 --- a/das_client/lib/model/journey/curve_point.dart +++ b/das_client/lib/model/journey/curve_point.dart @@ -13,6 +13,11 @@ class CurvePoint extends BaseData { final CurvePointType? curvePointType; final CurveType? curveType; final String? comment; + + @override + String toString() { + return "CurvePoint(order: $order, kilometre: $kilometre, curvePointType: $curvePointType, curveType: $curveType, comment: '$comment')"; + } } /// marks the beginning and the end of a curve diff --git a/das_client/lib/model/journey/protection_section.dart b/das_client/lib/model/journey/protection_section.dart index c7b18b15..9736f599 100644 --- a/das_client/lib/model/journey/protection_section.dart +++ b/das_client/lib/model/journey/protection_section.dart @@ -7,4 +7,9 @@ class ProtectionSection extends BaseData { final bool isOptional; final bool isLong; + + @override + String toString() { + return 'ProtectionSection(order: $order, kilometre: $kilometre, isOptional: $isOptional, isLong: $isLong)'; + } } diff --git a/das_client/lib/model/journey/service_point.dart b/das_client/lib/model/journey/service_point.dart index 835ba81c..06e27a17 100644 --- a/das_client/lib/model/journey/service_point.dart +++ b/das_client/lib/model/journey/service_point.dart @@ -19,4 +19,9 @@ class ServicePoint extends BaseData { final bool isStop; final bool isStation; final BracketStation? bracketStation; + + @override + String toString() { + return 'ServicePoint(order: $order, kilometre: $kilometre, name: $name, mandatoryStop: $mandatoryStop, isStop: $isStop, isStation: $isStation, bracketStation: $bracketStation)'; + } } diff --git a/das_client/lib/model/journey/signal.dart b/das_client/lib/model/journey/signal.dart index 712f90d1..87caa4b3 100644 --- a/das_client/lib/model/journey/signal.dart +++ b/das_client/lib/model/journey/signal.dart @@ -11,6 +11,11 @@ class Signal extends BaseData { final List functions; final String? visualIdentifier; + + @override + String toString() { + return 'Signal(order: $order, kilometre: $kilometre, functions: $functions, visualIdentifier: $visualIdentifier)'; + } } enum SignalFunction { diff --git a/das_client/lib/model/journey/track_equipment.dart b/das_client/lib/model/journey/track_equipment.dart index 14ea01fe..62e1e953 100644 --- a/das_client/lib/model/journey/track_equipment.dart +++ b/das_client/lib/model/journey/track_equipment.dart @@ -1,4 +1,7 @@ -class NonStandardTrackEquipmentSegment { +import 'package:das_client/util/comparators.dart'; + +/// Represents a segment with non standard track equipment. Standard is bidirectional ETCS L1LS. +class NonStandardTrackEquipmentSegment implements Comparable { const NonStandardTrackEquipmentSegment({ required this.startKm, required this.endKm, @@ -9,11 +12,40 @@ class NonStandardTrackEquipmentSegment { final List startKm; final List endKm; - final int startOrder; - final int endOrder; final TrackEquipmentType type; - bool appliesToOrder(int order) => startOrder <= order && order <= endOrder; + /// Start order of this segment. Nullable as it can start in a journey segment that is not part of the train journey. + final int? startOrder; + + /// End order of this segment. Nullable as it can end in a journey segment that is not part of the train journey. + final int? endOrder; + + bool get isEtcsL2Segment => type.isEtcsL2; + + /// checks if the given order is part of this segment. + bool appliesToOrder(int order) { + if (startOrder != null && endOrder != null) { + return startOrder! <= order && order <= endOrder!; + } else if (startOrder != null) { + return startOrder! <= order; + } else { + return order <= endOrder!; + } + } + + @override + int compareTo(other) { + if (other is! NonStandardTrackEquipmentSegment) return -1; + + final startEnd = (start: startOrder, end: endOrder); + final otherStartEnd = (start: other.startOrder, end: other.endOrder); + return StartEndIntComparator.compare(startEnd, otherStartEnd); + } + + @override + String toString() { + return 'NonStandardTrackEquipmentSegment(startKm: $startKm, endKm: $endKm, startOrder: $startOrder, endOrder: $endOrder, type: $type)'; + } } enum TrackEquipmentType { @@ -22,7 +54,7 @@ enum TrackEquipmentType { etcsL2ExtSpeedReversingPossible, etcsL2ExtSpeedReversingImpossible; - bool isEtcsL2() => [ + bool get isEtcsL2 => [ TrackEquipmentType.etcsL2ConvSpeedReversingImpossible, TrackEquipmentType.etcsL2ExtSpeedReversingPossible, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible @@ -31,19 +63,24 @@ enum TrackEquipmentType { // extensions -extension NonStandardTrackEquipmentSegmentsExtension on List { - List appliesToOrder(int order) => - where((segment) => segment.appliesToOrder(order)).toList(); +extension NonStandardTrackEquipmentSegmentsExtension on Iterable { + Iterable appliesToOrder(int order) => + where((segment) => segment.appliesToOrder(order)); /// Returns all [NonStandardTrackEquipmentSegment] of this list that mark the start of a ETCS level 2 segment - List get withCABSignalingStart { - final etcsL2Segments = where((segment) => segment.type.isEtcsL2()).toList(); - etcsL2Segments.sort((a, b) => a.startOrder.compareTo(b.startOrder)); - final starts = []; + Iterable get withCABSignalingStart { + final etcsL2Segments = where((segment) => segment.type.isEtcsL2).toList(); + etcsL2Segments.sort(); + final starts = []; for (int i = 0; i < etcsL2Segments.length; i++) { - if (i == 0 || etcsL2Segments[i].startOrder != etcsL2Segments[i - 1].endOrder) { - starts.add(etcsL2Segments[i]); + final segment = etcsL2Segments[i]; + + // ignore segments without start + if (segment.startOrder == null) continue; + + if (i == 0 || segment.startOrder != etcsL2Segments[i - 1].endOrder) { + starts.add(segment); } } @@ -51,14 +88,19 @@ extension NonStandardTrackEquipmentSegmentsExtension on List get withCABSignalingEnd { - final etcsL2Segments = where((segment) => segment.type.isEtcsL2()).toList(); - etcsL2Segments.sort((a, b) => a.startOrder.compareTo(b.startOrder)); - final ends = []; + Iterable get withCABSignalingEnd { + final etcsL2Segments = where((segment) => segment.type.isEtcsL2).toList(); + etcsL2Segments.sort(); + final ends = []; for (int i = 0; i < etcsL2Segments.length; i++) { - if (i == etcsL2Segments.length - 1 || etcsL2Segments[i].endOrder != etcsL2Segments[i + 1].startOrder) { - ends.add(etcsL2Segments[i]); + final segment = etcsL2Segments[i]; + + // ignore segments without end + if (segment.endOrder == null) continue; + + if (i == etcsL2Segments.length - 1 || segment.endOrder != etcsL2Segments[i + 1].startOrder) { + ends.add(segment); } } diff --git a/das_client/lib/model/localized_string.dart b/das_client/lib/model/localized_string.dart index 6c6b1b5f..ed5f5c2f 100644 --- a/das_client/lib/model/localized_string.dart +++ b/das_client/lib/model/localized_string.dart @@ -23,4 +23,9 @@ class LocalizedString { return de ?? fr ?? it ?? ''; } } + + @override + String toString() { + return "LocalizedString(de: '$de', fr: '$fr', it: '$it')"; + } } diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart index 95860af2..18745afb 100644 --- a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -105,7 +105,7 @@ class SferaModelMapper { journeyData.addAll(_cabSignalingStart(trackEquipmentSegments)); journeyData.addAll(_cabSignalingEnd(trackEquipmentSegments)); - journeyData.sort((a, b) => a.order.compareTo(b.order)); + journeyData.sort(); final servicePoints = journeyData.where((it) => it.type == Datatype.servicePoint).toList(); return Journey( @@ -306,13 +306,13 @@ class SferaModelMapper { }).toList(); } - static Iterable _cabSignalingStart(List trackEquipmentSegments) { + static Iterable _cabSignalingStart(Iterable trackEquipmentSegments) { return trackEquipmentSegments.withCABSignalingStart - .map((element) => CABSignaling(isStart: true, order: element.startOrder, kilometre: element.startKm)); + .map((element) => CABSignaling(isStart: true, order: element.startOrder!, kilometre: element.startKm)); } - static Iterable _cabSignalingEnd(List trackEquipmentSegments) { + static Iterable _cabSignalingEnd(Iterable trackEquipmentSegments) { return trackEquipmentSegments.withCABSignalingEnd - .map((element) => CABSignaling(isStart: false, order: element.endOrder, kilometre: element.endKm)); + .map((element) => CABSignaling(isStart: false, order: element.endOrder!, kilometre: element.endKm)); } } diff --git a/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart b/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart index 83562793..1f014c16 100644 --- a/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart +++ b/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart @@ -5,13 +5,14 @@ import 'package:das_client/sfera/src/model/enums/track_equipment_type.dart'; import 'package:das_client/sfera/src/model/network_specific_area.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; import 'package:das_client/sfera/src/model/segment_profile_list.dart'; +import 'package:das_client/util/comparators.dart'; import 'package:fimber/fimber.dart'; class TrackEquipmentMapper { TrackEquipmentMapper._(); static List parseNonStandardTrackEquipmentSegment( - List segmentProfilesLists, List segmentProfiles) { + Iterable segmentProfilesLists, Iterable segmentProfiles) { final trackEquipments = _parseTrackEquipments(segmentProfilesLists, segmentProfiles); trackEquipments.sort((a, b) => a.compareTo(b)); @@ -23,6 +24,7 @@ class TrackEquipmentMapper { segments.add(_createSegmentFromStartsEnds(trackEquipment)); } else if (trackEquipment.startLocation != null) { if (openStartSegments.containsKey(trackEquipment.type)) { + Fimber.w('Found a track equipment with the same type ${trackEquipment.type} that hasn\'t ended yet'); continue; } openStartSegments[trackEquipment.type] = trackEquipment; @@ -30,35 +32,61 @@ class TrackEquipmentMapper { final startOfSegment = openStartSegments[trackEquipment.type]; if (startOfSegment != null) { segments.add(_createSegment(startOfSegment, trackEquipment)); - - // Clear the pending start after creating a segment openStartSegments[trackEquipment.type] = null; + } else if (trackEquipment.segmentIndex == 0) { + // got end of track equipment with start outside of train journey + segments.add(_createSegmentFromEnds(trackEquipment)); + } else { + Fimber.w('Got end of track equipment segment for type ${trackEquipment.type} without start definition'); } } } + // check open start segments + for (final trackEquipment in openStartSegments.values) { + if (trackEquipment == null) continue; + + if (trackEquipment.segmentIndex == segmentProfilesLists.length - 1) { + // got end of track equipment with start outside of train journey + segments.add(_createSegmentFromStarts(trackEquipment)); + } else { + Fimber.w('Got start of track equipment segment for type ${trackEquipment.type} without end definition'); + } + } + return segments; } + static NonStandardTrackEquipmentSegment _createSegmentFromStarts(_NonStandardTrackEquipment startTrackEquipment) => + _createSegment(startTrackEquipment, null); + + static NonStandardTrackEquipmentSegment _createSegmentFromEnds(_NonStandardTrackEquipment endTrackEquipment) => + _createSegment(null, endTrackEquipment); + static NonStandardTrackEquipmentSegment _createSegmentFromStartsEnds(_NonStandardTrackEquipment trackEquipment) => _createSegment(trackEquipment, trackEquipment); static NonStandardTrackEquipmentSegment _createSegment( - _NonStandardTrackEquipment start, _NonStandardTrackEquipment end) { + _NonStandardTrackEquipment? start, _NonStandardTrackEquipment? end) { + if (start == null && end == null) { + throw Exception('Can not create track equipment segment without at least start or end information.'); + } + + final type = start?.type ?? end?.type; return NonStandardTrackEquipmentSegment( - type: start.type.toTrackEquipmentType(), - startOrder: SferaModelMapper.calculateOrder(start.index, start.startLocation!), - endOrder: SferaModelMapper.calculateOrder(end.index, end.endLocation!), - startKm: start.startKm, - endKm: end.endKm, + type: type!.trackEquipmentType, + startOrder: start != null ? SferaModelMapper.calculateOrder(start.segmentIndex, start.startLocation!) : null, + endOrder: end != null ? SferaModelMapper.calculateOrder(end.segmentIndex, end.endLocation!) : null, + startKm: start?.startKm ?? [], + endKm: end?.endKm ?? [], ); } static List<_NonStandardTrackEquipment> _parseTrackEquipments( - List segmentProfilesLists, List segmentProfiles) { + Iterable segmentProfilesLists, Iterable segmentProfiles) { final trackEquipments = <_NonStandardTrackEquipment>[]; for (int segmentIndex = 0; segmentIndex < segmentProfilesLists.length; segmentIndex++) { - final segmentProfile = segmentProfiles.firstMatch(segmentProfilesLists[segmentIndex]); + final segmentProfile = segmentProfiles.firstMatch(segmentProfilesLists.elementAt(segmentIndex)); final nonStandardTrackEquipments = segmentProfile.areas?.nonStandardTrackEquipments ?? []; final kilometreMap = SferaModelMapper.parseKilometre(segmentProfile); @@ -74,14 +102,15 @@ class TrackEquipmentMapper { return trackEquipments; } - static _NonStandardTrackEquipment? _mapToNonStandardTrackEquipment(NetworkSpecificArea element, int segmentIndex, Map> kilometreMap) { + static _NonStandardTrackEquipment? _mapToNonStandardTrackEquipment( + NetworkSpecificArea element, int segmentIndex, Map> kilometreMap) { if (element.trackEquipmentTypeWrapper == null) { - Fimber.w('Encountered nonStandardTrackEquipment track equipment type NSP declaration: ${element.type}'); + Fimber.w('Encountered invalid nonStandardTrackEquipment track equipment type NSP declaration: ${element.type}'); return null; } return _NonStandardTrackEquipment( - index: segmentIndex, + segmentIndex: segmentIndex, type: element.trackEquipmentTypeWrapper!.unwrapped, startLocation: element.startLocation, endLocation: element.endLocation, @@ -98,7 +127,7 @@ class _NonStandardTrackEquipment implements Comparable { required this.startKm, required this.endKm, required this.type, - required this.index, + required this.segmentIndex, this.startLocation, this.endLocation, this.appliesToWholeSp = false, @@ -110,34 +139,17 @@ class _NonStandardTrackEquipment implements Comparable { final List startKm; final List endKm; final bool appliesToWholeSp; - final int index; + final int segmentIndex; @override int compareTo(other) { - final indexComparison = index.compareTo(other.index); - if (indexComparison != 0) return indexComparison; + if (other is! _NonStandardTrackEquipment) return -1; - // If indexes are equal, compare the startLocation - if (startLocation != null && other.startLocation != null) { - final startLocationComparison = startLocation!.compareTo(other.startLocation!); - if (startLocationComparison != 0) { - return startLocationComparison; - } - } else if (startLocation != null) { - return -1; - } else if (other.startLocation != null) { - return 1; - } - - // If both startLocation are equal or not provided, compare the endLocation - if (endLocation != null && other.endLocation != null) { - return endLocation!.compareTo(other.endLocation!); - } else if (endLocation != null) { - return -1; - } else if (other.endLocation != null) { - return 1; - } + final indexComparison = segmentIndex.compareTo(other.segmentIndex); + if (indexComparison != 0) return indexComparison; - return 0; + final startEnd = (start: startLocation?.toInt(), end: endLocation?.toInt()); + final otherStartEnd = (start: other.startLocation?.toInt(), end: other.endLocation?.toInt()); + return StartEndIntComparator.compare(startEnd, otherStartEnd); } } diff --git a/das_client/lib/sfera/src/model/enums/track_equipment_type.dart b/das_client/lib/sfera/src/model/enums/track_equipment_type.dart index 23e5fb72..d396202e 100644 --- a/das_client/lib/sfera/src/model/enums/track_equipment_type.dart +++ b/das_client/lib/sfera/src/model/enums/track_equipment_type.dart @@ -2,28 +2,30 @@ import 'package:das_client/model/journey/track_equipment.dart'; import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; enum SferaTrackEquipmentType implements XmlEnum { - etcsL1ls2TracksWithSingleTrackEquipment(xmlValue: 'ETCS-L1LS-2TracksWithSingleTrackEquipment'), - etcsL2ConvSpeedReversingImpossible(xmlValue: 'ETCS-L2-convSpeedReversingImpossible'), - etcsL2ExtSpeedReversingPossible(xmlValue: 'ETCS-L2-extSpeedReversingPossible'), - etcsL2ExtSpeedReversingImpossible(xmlValue: 'ETCS-L2-extSpeedReversingImpossible'); + etcsL1ls2TracksWithSingleTrackEquipment( + xmlValue: 'ETCS-L1LS-2TracksWithSingleTrackEquipment', + trackEquipmentType: TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment, + ), + etcsL2ConvSpeedReversingImpossible( + xmlValue: 'ETCS-L2-convSpeedReversingImpossible', + trackEquipmentType: TrackEquipmentType.etcsL2ConvSpeedReversingImpossible, + ), + etcsL2ExtSpeedReversingPossible( + xmlValue: 'ETCS-L2-extSpeedReversingPossible', + trackEquipmentType: TrackEquipmentType.etcsL2ExtSpeedReversingPossible, + ), + etcsL2ExtSpeedReversingImpossible( + xmlValue: 'ETCS-L2-extSpeedReversingImpossible', + trackEquipmentType: TrackEquipmentType.etcsL2ExtSpeedReversingImpossible, + ); const SferaTrackEquipmentType({ required this.xmlValue, + required this.trackEquipmentType, }); - TrackEquipmentType toTrackEquipmentType() { - switch(this) { - case SferaTrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment: - return TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment; - case SferaTrackEquipmentType.etcsL2ConvSpeedReversingImpossible: - return TrackEquipmentType.etcsL2ConvSpeedReversingImpossible; - case SferaTrackEquipmentType.etcsL2ExtSpeedReversingPossible: - return TrackEquipmentType.etcsL2ExtSpeedReversingPossible; - case SferaTrackEquipmentType.etcsL2ExtSpeedReversingImpossible: - return TrackEquipmentType.etcsL2ExtSpeedReversingImpossible; - } - } - @override final String xmlValue; + + final TrackEquipmentType trackEquipmentType; } diff --git a/das_client/lib/util/comparators.dart b/das_client/lib/util/comparators.dart new file mode 100644 index 00000000..74545c6d --- /dev/null +++ b/das_client/lib/util/comparators.dart @@ -0,0 +1,32 @@ +typedef StartEndInt = ({int? start, int? end}); + +/// compares two ranges with start and end int +class StartEndIntComparator { + StartEndIntComparator._(); + + static int compare(StartEndInt a, StartEndInt b) { + if (a.start != null && b.start != null) { + final startComparison = a.start!.compareTo(b.start!); + if (startComparison != 0) { + return startComparison; + } + } + + // if start is null, it is considered outside of range and smaller + if (a.start == null && b.start != null) { + return -1; + } else if (a.start != null && b.start == null) { + return 1; + } + + // if starts are not given, compare ends + // if end is null, it is considered outside of range and bigger + if (a.end != null && b.end != null) { + return a.end!.compareTo(b.end!); + } else if (a.end == null && b.end != null) { + return 1; + } else { + return -1; + } + } +} diff --git a/das_client/test/model/journey/track_equipment_test.dart b/das_client/test/model/journey/track_equipment_test.dart index c7bb1277..ea8e8f06 100644 --- a/das_client/test/model/journey/track_equipment_test.dart +++ b/das_client/test/model/journey/track_equipment_test.dart @@ -21,7 +21,35 @@ void main() { expect(onEnd, isTrue); expect(aboveEnd, isFalse); }); + test('Test track equipment sorting', () { + // given + final trackEquipment1 = _etcsL2ExtSpeedReversingPossible(null, 200); + final trackEquipment2 = _etcsL2ExtSpeedReversingImpossible(300, 500); + final trackEquipment3 = _etcsL2ConvSpeedReversingImpossible(500, 800); + final trackEquipment4 = _etcsL2ExtSpeedReversingImpossible(800, 900); + final trackEquipment5 = _etcsL2ExtSpeedReversingPossible(900, null); + final trackEquipments = [ + trackEquipment1, + trackEquipment2, + trackEquipment3, + trackEquipment4, + trackEquipment5, + ]; + + // when + trackEquipments.shuffle(); + trackEquipments.sort(); + // then + expect(trackEquipments[0], trackEquipment1); + expect(trackEquipments[1], trackEquipment2); + expect(trackEquipments[2], trackEquipment3); + expect(trackEquipments[3], trackEquipment4); + expect(trackEquipments[4], trackEquipment5); + }); + }); + + group('Test track equipment CAB signaling', () { test('Test CAB signaling start and end for connected segment', () { // given final trackEquipment1 = _etcsL2ExtSpeedReversingPossible(100, 300); @@ -72,11 +100,11 @@ void main() { // then expect(withCABSignalingStart, hasLength(2)); - expect(withCABSignalingStart[0], trackEquipment1Segment1); - expect(withCABSignalingStart[1], trackEquipment1Segment2); + expect(withCABSignalingStart.elementAt(0), trackEquipment1Segment1); + expect(withCABSignalingStart.elementAt(1), trackEquipment1Segment2); expect(withCABSignalingEnd, hasLength(2)); - expect(withCABSignalingEnd[0], trackEquipment2Segment1); - expect(withCABSignalingEnd[1], trackEquipment3Segment2); + expect(withCABSignalingEnd.elementAt(0), trackEquipment2Segment1); + expect(withCABSignalingEnd.elementAt(1), trackEquipment3Segment2); }); test('Test CAB signaling start and end with single track equipments and a L1LS in between', () { @@ -97,11 +125,11 @@ void main() { // then expect(withCABSignalingStart, hasLength(2)); - expect(withCABSignalingStart[0], trackEquipment1); - expect(withCABSignalingStart[1], trackEquipment3); + expect(withCABSignalingStart.elementAt(0), trackEquipment1); + expect(withCABSignalingStart.elementAt(1), trackEquipment3); expect(withCABSignalingEnd, hasLength(2)); - expect(withCABSignalingEnd[0], trackEquipment1); - expect(withCABSignalingEnd[1], trackEquipment3); + expect(withCABSignalingEnd.elementAt(0), trackEquipment1); + expect(withCABSignalingEnd.elementAt(1), trackEquipment3); }); test('Test CAB signaling start and end with single track equipment', () { @@ -116,19 +144,44 @@ void main() { // then expect(withCABSignalingStart, hasLength(1)); - expect(withCABSignalingStart[0], trackEquipment1); + expect(withCABSignalingStart.elementAt(0), trackEquipment1); expect(withCABSignalingEnd, hasLength(1)); - expect(withCABSignalingEnd[0], trackEquipment1); + expect(withCABSignalingEnd.elementAt(0), trackEquipment1); + }); + + test('Test CAB signaling with track equipments that start and end outside train journey', () { + // given + final startOutsideJourney = _etcsL2ExtSpeedReversingPossible(null, 100); + final insideJourney = _etcsL2ExtSpeedReversingPossible(300, 500); + final endOutsideJourney = _etcsL2ExtSpeedReversingPossible(700, null); + + final trackEquipments = [ + startOutsideJourney, + insideJourney, + endOutsideJourney, + ]; + + // when + final withCABSignalingStart = trackEquipments.withCABSignalingStart; + final withCABSignalingEnd = trackEquipments.withCABSignalingEnd; + + // then + expect(withCABSignalingStart, hasLength(2)); + expect(withCABSignalingStart.elementAt(0), insideJourney); + expect(withCABSignalingStart.elementAt(1), endOutsideJourney); + expect(withCABSignalingEnd, hasLength(2)); + expect(withCABSignalingEnd.elementAt(0), startOutsideJourney); + expect(withCABSignalingEnd.elementAt(1), insideJourney); }); }); group('Test track equipment type', () { test('Test if type is ETCS level 2', () { // when - final tracksWithSingleTrackEquipment = TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment.isEtcsL2(); - final convSpeedReversingImpossible = TrackEquipmentType.etcsL2ConvSpeedReversingImpossible.isEtcsL2(); - final extSpeedReversingPossible = TrackEquipmentType.etcsL2ExtSpeedReversingPossible.isEtcsL2(); - final extSpeedReversingImpossible = TrackEquipmentType.etcsL2ConvSpeedReversingImpossible.isEtcsL2(); + final tracksWithSingleTrackEquipment = TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment.isEtcsL2; + final convSpeedReversingImpossible = TrackEquipmentType.etcsL2ConvSpeedReversingImpossible.isEtcsL2; + final extSpeedReversingPossible = TrackEquipmentType.etcsL2ExtSpeedReversingPossible.isEtcsL2; + final extSpeedReversingImpossible = TrackEquipmentType.etcsL2ConvSpeedReversingImpossible.isEtcsL2; // then expect(tracksWithSingleTrackEquipment, isFalse); @@ -139,7 +192,7 @@ void main() { }); } -NonStandardTrackEquipmentSegment _etcsL2ExtSpeedReversingPossible(int startOrder, int endOrder) { +NonStandardTrackEquipmentSegment _etcsL2ExtSpeedReversingPossible(int? startOrder, int? endOrder) { return NonStandardTrackEquipmentSegment( type: TrackEquipmentType.etcsL2ExtSpeedReversingPossible, startOrder: startOrder, @@ -149,7 +202,7 @@ NonStandardTrackEquipmentSegment _etcsL2ExtSpeedReversingPossible(int startOrder ); } -NonStandardTrackEquipmentSegment _etcsL2ExtSpeedReversingImpossible(int startOrder, int endOrder) { +NonStandardTrackEquipmentSegment _etcsL2ExtSpeedReversingImpossible(int? startOrder, int? endOrder) { return NonStandardTrackEquipmentSegment( type: TrackEquipmentType.etcsL2ExtSpeedReversingImpossible, startOrder: startOrder, @@ -159,7 +212,7 @@ NonStandardTrackEquipmentSegment _etcsL2ExtSpeedReversingImpossible(int startOrd ); } -NonStandardTrackEquipmentSegment _etcsL2ConvSpeedReversingImpossible(int startOrder, int endOrder) { +NonStandardTrackEquipmentSegment _etcsL2ConvSpeedReversingImpossible(int? startOrder, int? endOrder) { return NonStandardTrackEquipmentSegment( type: TrackEquipmentType.etcsL2ConvSpeedReversingImpossible, startOrder: startOrder, @@ -169,7 +222,7 @@ NonStandardTrackEquipmentSegment _etcsL2ConvSpeedReversingImpossible(int startOr ); } -NonStandardTrackEquipmentSegment _etcsL1ls2TracksWithSingleTrackEquipment(int startOrder, int endOrder) { +NonStandardTrackEquipmentSegment _etcsL1ls2TracksWithSingleTrackEquipment(int? startOrder, int? endOrder) { return NonStandardTrackEquipmentSegment( type: TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment, startOrder: startOrder, diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart index 3b1828e9..eabe829f 100644 --- a/das_client/test/sfera/mapper/sfera_mapper_test.dart +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -67,123 +67,141 @@ void main() { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(21)); + expect(journey.data, hasLength(25)); // segment 1 expect(journey.data[0], TypeMatcher()); expect(journey.data[1], TypeMatcher()); - expect(journey.data[2], TypeMatcher()); - expect(journey.data[3], TypeMatcher()); + expect(journey.data[2], TypeMatcher()); + expect(journey.data[3], TypeMatcher()); + expect(journey.data[4], TypeMatcher()); // segment 2 - expect(journey.data[4], TypeMatcher()); - expect(journey.data[5], TypeMatcher()); - expect(journey.data[6], TypeMatcher()); - expect(journey.data[7], TypeMatcher()); - expect(journey.data[8], TypeMatcher()); - // segment 3 - expect(journey.data[9], TypeMatcher()); - expect(journey.data[10], TypeMatcher()); + expect(journey.data[5], TypeMatcher()); + expect(journey.data[6], TypeMatcher()); + expect(journey.data[7], TypeMatcher()); + expect(journey.data[8], TypeMatcher()); + expect(journey.data[9], TypeMatcher()); + expect(journey.data[10], TypeMatcher()); expect(journey.data[11], TypeMatcher()); - expect(journey.data[12], TypeMatcher()); - // segment 4 - expect(journey.data[13], TypeMatcher()); - expect(journey.data[14], TypeMatcher()); + // segment 3 + expect(journey.data[12], TypeMatcher()); + expect(journey.data[13], TypeMatcher()); + expect(journey.data[14], TypeMatcher()); expect(journey.data[15], TypeMatcher()); - // segment 5 - expect(journey.data[16], TypeMatcher()); - expect(journey.data[17], TypeMatcher()); + // segment 4 + expect(journey.data[16], TypeMatcher()); + expect(journey.data[17], TypeMatcher()); expect(journey.data[18], TypeMatcher()); - expect(journey.data[19], TypeMatcher()); - expect(journey.data[20], TypeMatcher()); + // segment 5 + expect(journey.data[19], TypeMatcher()); + expect(journey.data[20], TypeMatcher()); + expect(journey.data[21], TypeMatcher()); + expect(journey.data[22], TypeMatcher()); + expect(journey.data[23], TypeMatcher()); + expect(journey.data[24], TypeMatcher()); }); test('Test kilometre are parsed correctly', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(21)); + expect(journey.data, hasLength(25)); // segment 1 expect(journey.data[0].kilometre[0], 0.2); expect(journey.data[1].kilometre[0], 0.5); - expect(journey.data[2].kilometre[0], 0.6); - expect(journey.data[3].kilometre[0], 0.7); + expect(journey.data[2].kilometre[0], 0.55); + expect(journey.data[3].kilometre[0], 0.6); + expect(journey.data[4].kilometre[0], 0.7); // segment 2 - expect(journey.data[4].kilometre[0], 1.1); - expect(journey.data[5].kilometre[0], 1.2); - expect(journey.data[6].kilometre[0], 1.5); - expect(journey.data[7].kilometre[0], 1.7); - expect(journey.data[8].kilometre[0], 1.8); + expect(journey.data[5].kilometre[0], 1.1); + expect(journey.data[6].kilometre[0], 1.2); + expect(journey.data[7].kilometre[0], 1.5); + expect(journey.data[8].kilometre[0], 1.5); + expect(journey.data[9].kilometre[0], 1.7); + expect(journey.data[10].kilometre[0], 1.7); + expect(journey.data[11].kilometre[0], 1.8); // segment 3 - expect(journey.data[9].kilometre[0], 2.1); - expect(journey.data[10].kilometre[0], 2.4); - expect(journey.data[11].kilometre[0], 2.5); - expect(journey.data[12].kilometre[0], 2.6); + expect(journey.data[12].kilometre[0], 2.1); + expect(journey.data[13].kilometre[0], 2.4); + expect(journey.data[14].kilometre[0], 2.5); + expect(journey.data[15].kilometre[0], 2.6); // segment 4 - expect(journey.data[13].kilometre[0], 3.5); - expect(journey.data[14].kilometre[0], 3.7); - expect(journey.data[14].kilometre[1], 0); - expect(journey.data[15].kilometre[0], 0.2); + expect(journey.data[16].kilometre[0], 3.5); + expect(journey.data[17].kilometre[0], 3.7); + expect(journey.data[17].kilometre[1], 0); + expect(journey.data[18].kilometre[0], 0.2); // segment 5 - expect(journey.data[16].kilometre[0], 0.6); - expect(journey.data[17].kilometre[0], 0.6); - expect(journey.data[18].kilometre[0], 0.9); - expect(journey.data[19].kilometre[0], 1.0); - expect(journey.data[20].kilometre[0], 1.1); + expect(journey.data[19].kilometre[0], 0.6); + expect(journey.data[20].kilometre[0], 0.6); + expect(journey.data[21].kilometre[0], 0.9); + expect(journey.data[22].kilometre[0], 0.9); + expect(journey.data[23].kilometre[0], 1.0); + expect(journey.data[24].kilometre[0], 1.1); }); test('Test order is generated correctly', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(21)); + expect(journey.data, hasLength(25)); // segment 1 expect(journey.data[0].order, 000200); expect(journey.data[1].order, 000500); - expect(journey.data[2].order, 000600); - expect(journey.data[3].order, 000700); + expect(journey.data[2].order, 000550); + expect(journey.data[3].order, 000600); + expect(journey.data[4].order, 000700); // segment 2 - expect(journey.data[4].order, 100100); - expect(journey.data[5].order, 100200); - expect(journey.data[6].order, 100500); - expect(journey.data[7].order, 100700); - expect(journey.data[8].order, 100800); + expect(journey.data[5].order, 100100); + expect(journey.data[6].order, 100200); + expect(journey.data[7].order, 100500); + expect(journey.data[8].order, 100500); + expect(journey.data[9].order, 100700); + expect(journey.data[10].order, 100700); + expect(journey.data[11].order, 100800); // segment 3 - expect(journey.data[9].order, 200100); - expect(journey.data[10].order, 200400); - expect(journey.data[11].order, 200500); - expect(journey.data[12].order, 200600); + expect(journey.data[12].order, 200100); + expect(journey.data[13].order, 200400); + expect(journey.data[14].order, 200500); + expect(journey.data[15].order, 200600); // segment 4 - expect(journey.data[13].order, 300500); - expect(journey.data[14].order, 300700); - expect(journey.data[15].order, 300900); + expect(journey.data[16].order, 300500); + expect(journey.data[17].order, 300700); + expect(journey.data[18].order, 300900); // segment 5 - expect(journey.data[16].order, 400300); - expect(journey.data[17].order, 400300); - expect(journey.data[18].order, 400600); - expect(journey.data[19].order, 400700); - expect(journey.data[20].order, 400800); + expect(journey.data[19].order, 400300); + expect(journey.data[20].order, 400300); + expect(journey.data[21].order, 400600); + expect(journey.data[22].order, 400600); + expect(journey.data[23].order, 400700); + expect(journey.data[24].order, 400800); }); test('Test track equipment is generated correctly', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.metadata.nonStandardTrackEquipmentSegment, hasLength(4)); - - expect(journey.metadata.nonStandardTrackEquipmentSegment[0].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); - expect(journey.metadata.nonStandardTrackEquipmentSegment[0].startOrder, 000600); - expect(journey.metadata.nonStandardTrackEquipmentSegment[0].endOrder, 000900); - expect(journey.metadata.nonStandardTrackEquipmentSegment[1].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); - expect(journey.metadata.nonStandardTrackEquipmentSegment[1].startOrder, 100100); - expect(journey.metadata.nonStandardTrackEquipmentSegment[1].endOrder, 100700); - expect(journey.metadata.nonStandardTrackEquipmentSegment[2].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); - expect(journey.metadata.nonStandardTrackEquipmentSegment[2].startOrder, 100700); - expect(journey.metadata.nonStandardTrackEquipmentSegment[2].endOrder, 300500); - expect(journey.metadata.nonStandardTrackEquipmentSegment[3].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); - expect(journey.metadata.nonStandardTrackEquipmentSegment[3].startOrder, 400300); - expect(journey.metadata.nonStandardTrackEquipmentSegment[3].endOrder, 400700); + expect(journey.metadata.nonStandardTrackEquipmentSegment, hasLength(6)); + + expect(journey.metadata.nonStandardTrackEquipmentSegment[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect(journey.metadata.nonStandardTrackEquipmentSegment[0].startOrder, isNull); + expect(journey.metadata.nonStandardTrackEquipmentSegment[0].endOrder, 000550); + expect(journey.metadata.nonStandardTrackEquipmentSegment[1].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); + expect(journey.metadata.nonStandardTrackEquipmentSegment[1].startOrder, 000600); + expect(journey.metadata.nonStandardTrackEquipmentSegment[1].endOrder, 000900); + expect(journey.metadata.nonStandardTrackEquipmentSegment[2].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegment[2].startOrder, 100100); + expect(journey.metadata.nonStandardTrackEquipmentSegment[2].endOrder, 100500); + expect(journey.metadata.nonStandardTrackEquipmentSegment[3].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect(journey.metadata.nonStandardTrackEquipmentSegment[3].startOrder, 100700); + expect(journey.metadata.nonStandardTrackEquipmentSegment[3].endOrder, 300500); + expect(journey.metadata.nonStandardTrackEquipmentSegment[4].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegment[4].startOrder, 400300); + expect(journey.metadata.nonStandardTrackEquipmentSegment[4].endOrder, 400600); + expect(journey.metadata.nonStandardTrackEquipmentSegment[5].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegment[5].startOrder, 400700); + expect(journey.metadata.nonStandardTrackEquipmentSegment[5].endOrder, isNull); }); test('Test signals are generated correctly', () async { diff --git a/das_client/test_resources/sp/SFERA_SP_9999_1.xml b/das_client/test_resources/sp/SFERA_SP_9999_1.xml index 9fe83e57..e3a0f659 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_1.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_1.xml @@ -68,6 +68,10 @@ + + + + @@ -81,6 +85,9 @@ + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_2.xml b/das_client/test_resources/sp/SFERA_SP_9999_2.xml index 923dcc78..6f2821b0 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_2.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_2.xml @@ -45,7 +45,7 @@ - + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_5.xml b/das_client/test_resources/sp/SFERA_SP_9999_5.xml index 56079c24..3350c7b0 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_5.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_5.xml @@ -50,7 +50,11 @@ - + + + + + From 0f2d05bb66e1c4669888236af852f0dcbe56adab Mon Sep 17 00:00:00 2001 From: u221638 Date: Fri, 6 Dec 2024 13:52:26 +0100 Subject: [PATCH 03/22] chore: fixed some issues after merge. --- .../sfera/src/mapper/sfera_model_mapper.dart | 7 +++--- .../model/track_equipment_type_wrapper.dart | 2 +- .../test/sfera/mapper/sfera_mapper_test.dart | 22 +++++++++---------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart index 837caffe..ba802700 100644 --- a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -27,7 +27,6 @@ import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/multilingual_text.dart'; import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; -import 'package:das_client/sfera/src/model/segment_profile_list.dart'; import 'package:das_client/sfera/src/model/speeds.dart'; import 'package:das_client/sfera/src/model/taf_tap_location.dart'; import 'package:fimber/fimber.dart'; @@ -337,11 +336,11 @@ class SferaModelMapper { Map> kilometreMap, List newLineSpeeds) { final connectionTracks = segmentProfile.contextInformation?.connectionTracks ?? []; return connectionTracks.map((connectionTrack) { - final currentOrder = _calculateOrder(segmentIndex, connectionTrack.location); + final currentOrder = calculateOrder(segmentIndex, connectionTrack.location); final speedChange = newLineSpeeds.firstWhereOrNull((it) => it.order == currentOrder); return ConnectionTrack( text: connectionTrack.connectionTrackDescription, - order: _calculateOrder(segmentIndex, connectionTrack.location), + order: calculateOrder(segmentIndex, connectionTrack.location), speedData: speedChange?.speedData, kilometre: kilometreMap[connectionTrack.location] ?? []); }).toList(); @@ -354,7 +353,7 @@ class SferaModelMapper { return SpeedChange( text: newLineSpeed.xmlNewLineSpeed.element.text, speedData: _speedDataFromSpeeds(newLineSpeed.xmlNewLineSpeed.element.speeds), - order: _calculateOrder(segmentIndex, newLineSpeed.location), + order: calculateOrder(segmentIndex, newLineSpeed.location), kilometre: kilometreMap[newLineSpeed.location] ?? []); }).toList(); } diff --git a/das_client/lib/sfera/src/model/track_equipment_type_wrapper.dart b/das_client/lib/sfera/src/model/track_equipment_type_wrapper.dart index b76f4647..e0b938f3 100644 --- a/das_client/lib/sfera/src/model/track_equipment_type_wrapper.dart +++ b/das_client/lib/sfera/src/model/track_equipment_type_wrapper.dart @@ -11,6 +11,6 @@ class TrackEquipmentTypeWrapper extends NetworkSpecificParameter { @override bool validate() { - return validateHasEnumAttribute(SferaTrackEquipmentType.values, 'value') && super.validate(); + return validateHasAttributeInRange('value', XmlEnum.values(SferaTrackEquipmentType.values)) && super.validate(); } } \ No newline at end of file diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart index 7a7ff180..a3c2a335 100644 --- a/das_client/test/sfera/mapper/sfera_mapper_test.dart +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -95,8 +95,8 @@ void main() { expect(journey.data[17], TypeMatcher()); expect(journey.data[18], TypeMatcher()); // segment 4 - expect(journey.data[19], TypeMatcher()); - expect(journey.data[20], TypeMatcher()); + expect(journey.data[19], TypeMatcher()); + expect(journey.data[20], TypeMatcher()); expect(journey.data[21], TypeMatcher()); expect(journey.data[22], TypeMatcher()); expect(journey.data[23], TypeMatcher()); @@ -139,12 +139,12 @@ void main() { expect(journey.data[18].kilometre[0], 2.6); // segment 4 expect(journey.data[19].kilometre[0], 3.5); - expect(journey.data[20].kilometre[0], 3.7); - expect(journey.data[20].kilometre[1], 0); - expect(journey.data[21].kilometre[0], 0.1); - expect(journey.data[22].kilometre[0], 0.2); + expect(journey.data[20].kilometre[0], 3.5); + expect(journey.data[21].kilometre[0], 3.7); + expect(journey.data[21].kilometre[1], 0); + expect(journey.data[22].kilometre[0], 0.1); + expect(journey.data[23].kilometre[0], 0.2); // segment 5 - expect(journey.data[23].kilometre[0], 0.4); expect(journey.data[24].kilometre[0], 0.6); expect(journey.data[25].kilometre[0], 0.6); expect(journey.data[26].kilometre[0], 0.9); @@ -183,11 +183,11 @@ void main() { expect(journey.data[18].order, 200600); // segment 4 expect(journey.data[19].order, 300500); - expect(journey.data[20].order, 300700); - expect(journey.data[21].order, 300800); - expect(journey.data[22].order, 300900); + expect(journey.data[20].order, 300500); + expect(journey.data[21].order, 300700); + expect(journey.data[22].order, 300800); + expect(journey.data[23].order, 300900); // segment 5 - expect(journey.data[23].order, 400100); expect(journey.data[24].order, 400300); expect(journey.data[25].order, 400300); expect(journey.data[26].order, 400600); From 7babb878ebacdb00409632be58b0712b1620b650 Mon Sep 17 00:00:00 2001 From: u221638 Date: Fri, 6 Dec 2024 15:44:17 +0100 Subject: [PATCH 04/22] chore: added UI integration test for CAB signaling. --- .../test/train_journey_table_test.dart | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/das_client/integration_test/test/train_journey_table_test.dart b/das_client/integration_test/test/train_journey_table_test.dart index db2ad6a1..2ff6d766 100644 --- a/das_client/integration_test/test/train_journey_table_test.dart +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -1,4 +1,5 @@ import 'package:das_client/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/curve_point_row.dart'; @@ -412,6 +413,59 @@ void main() { find.descendant(of: blockIntermediateSignalRow, matching: find.byKey(SignalRow.signalLineChangeIconKey)); expect(noLaneChangeIcon2, findsNothing); }); + + + + testWidgets('test if CAB signaling is displayed correctly', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: '9999'); + + final scrollableFinder = find.byType(ListView); + expect(scrollableFinder, findsOneWidget); + + // CAB segment with start outside train journey and end at 0.6 km + await tester.dragUntilVisible(find.text('0.6').first, scrollableFinder, const Offset(0, -50)); + final segment1CABStop = findDASTableRowByText('0.6').first; + final segment1CABStopIcon = find.descendant(of: segment1CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); + expect(segment1CABStopIcon, findsOneWidget); + + // CAB segment from km 1.1 to 1.5 + await tester.dragUntilVisible(find.text('1.1'), scrollableFinder, const Offset(0, -50)); + final segment2CABStart = findDASTableRowByText('1.1'); + final segment2CABStartIcon = find.descendant(of: segment2CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); + expect(segment2CABStartIcon, findsOneWidget); + await tester.dragUntilVisible(find.text('1.7'), scrollableFinder, const Offset(0, -50)); + final segment2CABStop = findDASTableRowByText('1.5').last; + final segment2CABStopIcon = find.descendant(of: segment2CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); + expect(segment2CABStopIcon, findsOneWidget); + + // CAB segment from km 1.7 to 3.5 + await tester.dragUntilVisible(find.text('1.8'), scrollableFinder, const Offset(0, -50)); + final segment3CABStart = findDASTableRowByText('1.7').last; + final segment3CABStartIcon = find.descendant(of: segment3CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); + expect(segment3CABStartIcon, findsOneWidget); + await tester.dragUntilVisible(find.text('3.7'), scrollableFinder, const Offset(0, -50)); + final segment3CABStop = findDASTableRowByText('3.5').last; + final segment3CABStopIcon = find.descendant(of: segment3CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); + expect(segment3CABStopIcon, findsOneWidget); + + // CAB segment from km 0.6 to 0.9 + await tester.dragUntilVisible(find.text('BAB1'), scrollableFinder, const Offset(0, -50)); + final segment4CABStart = findDASTableRowByText('0.6').last; + final segment4CABStartIcon = find.descendant(of: segment4CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); + expect(segment4CABStartIcon, findsOneWidget); + final segment4CABStop = findDASTableRowByText('0.9').last; + final segment4CABStopIcon = find.descendant(of: segment4CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); + expect(segment4CABStopIcon, findsOneWidget); + + // CAB segment with end outside train journey and start at 1.0 km + await tester.dragUntilVisible(find.text('1.0'), scrollableFinder, const Offset(0, -50)); + final segment5CABStart = findDASTableRowByText('1.0').first; + final segment5CABStartIcon = find.descendant(of: segment5CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); + expect(segment5CABStartIcon, findsOneWidget); + }); }); } From 331306520c73d855e45bfa65d3f2b4e09bb4d59f Mon Sep 17 00:00:00 2001 From: u221638 Date: Tue, 10 Dec 2024 16:57:43 +0100 Subject: [PATCH 05/22] chore: changes from review and fixed ordering of CABSignaling. --- das_client/lib/model/journey/base_data.dart | 12 ++++- .../lib/model/journey/cab_signaling.dart | 9 +--- das_client/lib/model/journey/metadata.dart | 4 +- .../sfera/src/mapper/sfera_model_mapper.dart | 2 +- .../src/mapper/track_equipment_mapper.dart | 24 ++++------ das_client/lib/util/comparators.dart | 6 ++- .../test/sfera/mapper/sfera_mapper_test.dart | 48 +++++++++---------- 7 files changed, 52 insertions(+), 53 deletions(-) diff --git a/das_client/lib/model/journey/base_data.dart b/das_client/lib/model/journey/base_data.dart index 4675d44b..03f81fca 100644 --- a/das_client/lib/model/journey/base_data.dart +++ b/das_client/lib/model/journey/base_data.dart @@ -16,7 +16,15 @@ abstract class BaseData implements Comparable { @override int compareTo(other) { - if(other is! BaseData) return -1; - return order.compareTo(other.order); + if (other is! BaseData) return -1; + final orderCompare = order.compareTo(other.order); + if (orderCompare == 0) { + return orderPriority.compareTo(other.orderPriority); + } + return orderCompare; } + + /// Used for comparing if [order] is equal. + /// If [orderPriority] is smaller, this is ordered before other, a bigger value is ordered after other. + int get orderPriority => 0; } diff --git a/das_client/lib/model/journey/cab_signaling.dart b/das_client/lib/model/journey/cab_signaling.dart index 08d51fed..ca01c2e5 100644 --- a/das_client/lib/model/journey/cab_signaling.dart +++ b/das_client/lib/model/journey/cab_signaling.dart @@ -11,14 +11,7 @@ class CABSignaling extends BaseData { final bool isStart; @override - int compareTo(other) { - final comparison = super.compareTo(other); - if (comparison != 0) { - return comparison; - } - - return isStart ? -1 : 1; - } + int get orderPriority => isStart ? -1 : 1; @override String toString() { diff --git a/das_client/lib/model/journey/metadata.dart b/das_client/lib/model/journey/metadata.dart index 338f3986..09c7eab7 100644 --- a/das_client/lib/model/journey/metadata.dart +++ b/das_client/lib/model/journey/metadata.dart @@ -13,7 +13,7 @@ class Metadata { this.trainSeries = TrainSeries.R, this.breakSeries = 150, this.additionalSpeedRestrictions = const [], - this.nonStandardTrackEquipmentSegment = const [], + this.nonStandardTrackEquipmentSegments = const [], }); final ServicePoint? nextStop; @@ -21,7 +21,7 @@ class Metadata { final List additionalSpeedRestrictions; final BaseData? routeStart; final BaseData? routeEnd; - final List nonStandardTrackEquipmentSegment; + final List nonStandardTrackEquipmentSegments; final TrainSeries trainSeries; final int breakSeries; } diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart index ba802700..1284faec 100644 --- a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -131,7 +131,7 @@ class SferaModelMapper { additionalSpeedRestrictions: additionalSpeedRestrictions, routeStart: journeyData.firstOrNull, routeEnd: journeyData.lastOrNull, - nonStandardTrackEquipmentSegment: trackEquipmentSegments, + nonStandardTrackEquipmentSegments: trackEquipmentSegments, ), data: journeyData, ); diff --git a/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart b/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart index 1f014c16..ab41183d 100644 --- a/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart +++ b/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart @@ -14,44 +14,40 @@ class TrackEquipmentMapper { static List parseNonStandardTrackEquipmentSegment( Iterable segmentProfilesLists, Iterable segmentProfiles) { final trackEquipments = _parseTrackEquipments(segmentProfilesLists, segmentProfiles); - trackEquipments.sort((a, b) => a.compareTo(b)); + trackEquipments.sort(); - final openStartSegments = {}; + final openSegments = {}; final List segments = []; for (final trackEquipment in trackEquipments) { if (trackEquipment.startLocation != null && trackEquipment.endLocation != null) { segments.add(_createSegmentFromStartsEnds(trackEquipment)); } else if (trackEquipment.startLocation != null) { - if (openStartSegments.containsKey(trackEquipment.type)) { + if (openSegments.containsKey(trackEquipment.type)) { Fimber.w('Found a track equipment with the same type ${trackEquipment.type} that hasn\'t ended yet'); continue; } - openStartSegments[trackEquipment.type] = trackEquipment; + openSegments[trackEquipment.type] = trackEquipment; } else if (trackEquipment.endLocation != null) { - final startOfSegment = openStartSegments[trackEquipment.type]; + final startOfSegment = openSegments[trackEquipment.type]; if (startOfSegment != null) { segments.add(_createSegment(startOfSegment, trackEquipment)); - openStartSegments[trackEquipment.type] = null; + openSegments.remove(trackEquipment.type); } else if (trackEquipment.segmentIndex == 0) { // got end of track equipment with start outside of train journey segments.add(_createSegmentFromEnds(trackEquipment)); } else { Fimber.w('Got end of track equipment segment for type ${trackEquipment.type} without start definition'); } + } else if(trackEquipment.appliesToWholeSp) { + openSegments.putIfAbsent(trackEquipment.type, () => trackEquipment); } } // check open start segments - for (final trackEquipment in openStartSegments.values) { + for (final trackEquipment in openSegments.values) { if (trackEquipment == null) continue; - - if (trackEquipment.segmentIndex == segmentProfilesLists.length - 1) { - // got end of track equipment with start outside of train journey - segments.add(_createSegmentFromStarts(trackEquipment)); - } else { - Fimber.w('Got start of track equipment segment for type ${trackEquipment.type} without end definition'); - } + segments.add(_createSegmentFromStarts(trackEquipment)); } return segments; diff --git a/das_client/lib/util/comparators.dart b/das_client/lib/util/comparators.dart index 74545c6d..0f34543a 100644 --- a/das_client/lib/util/comparators.dart +++ b/das_client/lib/util/comparators.dart @@ -23,10 +23,12 @@ class StartEndIntComparator { // if end is null, it is considered outside of range and bigger if (a.end != null && b.end != null) { return a.end!.compareTo(b.end!); - } else if (a.end == null && b.end != null) { + } else if (a.end == null) { return 1; - } else { + } else if (b.end == null) { return -1; } + + return 0; } } diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart index a3c2a335..0680d914 100644 --- a/das_client/test/sfera/mapper/sfera_mapper_test.dart +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -84,8 +84,8 @@ void main() { expect(journey.data[7], TypeMatcher()); expect(journey.data[8], TypeMatcher()); expect(journey.data[9], TypeMatcher()); - expect(journey.data[10], TypeMatcher()); - expect(journey.data[11], TypeMatcher()); + expect(journey.data[10], TypeMatcher()); + expect(journey.data[11], TypeMatcher()); expect(journey.data[12], TypeMatcher()); // segment 3 expect(journey.data[13], TypeMatcher()); @@ -101,8 +101,8 @@ void main() { expect(journey.data[22], TypeMatcher()); expect(journey.data[23], TypeMatcher()); // segment 5 - expect(journey.data[24], TypeMatcher()); - expect(journey.data[25], TypeMatcher()); + expect(journey.data[24], TypeMatcher()); + expect(journey.data[25], TypeMatcher()); expect(journey.data[26], TypeMatcher()); expect(journey.data[27], TypeMatcher()); expect(journey.data[28], TypeMatcher()); @@ -200,26 +200,26 @@ void main() { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.metadata.nonStandardTrackEquipmentSegment, hasLength(6)); - - expect(journey.metadata.nonStandardTrackEquipmentSegment[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); - expect(journey.metadata.nonStandardTrackEquipmentSegment[0].startOrder, isNull); - expect(journey.metadata.nonStandardTrackEquipmentSegment[0].endOrder, 000550); - expect(journey.metadata.nonStandardTrackEquipmentSegment[1].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); - expect(journey.metadata.nonStandardTrackEquipmentSegment[1].startOrder, 000600); - expect(journey.metadata.nonStandardTrackEquipmentSegment[1].endOrder, 000900); - expect(journey.metadata.nonStandardTrackEquipmentSegment[2].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); - expect(journey.metadata.nonStandardTrackEquipmentSegment[2].startOrder, 100100); - expect(journey.metadata.nonStandardTrackEquipmentSegment[2].endOrder, 100500); - expect(journey.metadata.nonStandardTrackEquipmentSegment[3].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); - expect(journey.metadata.nonStandardTrackEquipmentSegment[3].startOrder, 100700); - expect(journey.metadata.nonStandardTrackEquipmentSegment[3].endOrder, 300500); - expect(journey.metadata.nonStandardTrackEquipmentSegment[4].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); - expect(journey.metadata.nonStandardTrackEquipmentSegment[4].startOrder, 400300); - expect(journey.metadata.nonStandardTrackEquipmentSegment[4].endOrder, 400600); - expect(journey.metadata.nonStandardTrackEquipmentSegment[5].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); - expect(journey.metadata.nonStandardTrackEquipmentSegment[5].startOrder, 400700); - expect(journey.metadata.nonStandardTrackEquipmentSegment[5].endOrder, isNull); + expect(journey.metadata.nonStandardTrackEquipmentSegments, hasLength(6)); + + expect(journey.metadata.nonStandardTrackEquipmentSegments[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[0].startOrder, isNull); + expect(journey.metadata.nonStandardTrackEquipmentSegments[0].endOrder, 000550); + expect(journey.metadata.nonStandardTrackEquipmentSegments[1].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); + expect(journey.metadata.nonStandardTrackEquipmentSegments[1].startOrder, 000600); + expect(journey.metadata.nonStandardTrackEquipmentSegments[1].endOrder, 000900); + expect(journey.metadata.nonStandardTrackEquipmentSegments[2].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[2].startOrder, 100100); + expect(journey.metadata.nonStandardTrackEquipmentSegments[2].endOrder, 100500); + expect(journey.metadata.nonStandardTrackEquipmentSegments[3].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[3].startOrder, 100700); + expect(journey.metadata.nonStandardTrackEquipmentSegments[3].endOrder, 300500); + expect(journey.metadata.nonStandardTrackEquipmentSegments[4].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[4].startOrder, 400300); + expect(journey.metadata.nonStandardTrackEquipmentSegments[4].endOrder, 400600); + expect(journey.metadata.nonStandardTrackEquipmentSegments[5].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[5].startOrder, 400700); + expect(journey.metadata.nonStandardTrackEquipmentSegments[5].endOrder, isNull); }); test('Test signals are generated correctly', () async { From d707eca3f63af83c8d5f7d769c4760dbb6658997 Mon Sep 17 00:00:00 2001 From: u221638 Date: Tue, 10 Dec 2024 17:09:36 +0100 Subject: [PATCH 06/22] chore: fixed integration test. --- .../integration_test/test/train_journey_table_test.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/das_client/integration_test/test/train_journey_table_test.dart b/das_client/integration_test/test/train_journey_table_test.dart index 2ff6d766..8b567753 100644 --- a/das_client/integration_test/test/train_journey_table_test.dart +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -414,8 +414,6 @@ void main() { expect(noLaneChangeIcon2, findsNothing); }); - - testWidgets('test if CAB signaling is displayed correctly', (tester) async { await prepareAndStartApp(tester); @@ -443,7 +441,7 @@ void main() { // CAB segment from km 1.7 to 3.5 await tester.dragUntilVisible(find.text('1.8'), scrollableFinder, const Offset(0, -50)); - final segment3CABStart = findDASTableRowByText('1.7').last; + final segment3CABStart = findDASTableRowByText('1.7').first; final segment3CABStartIcon = find.descendant(of: segment3CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); expect(segment3CABStartIcon, findsOneWidget); await tester.dragUntilVisible(find.text('3.7'), scrollableFinder, const Offset(0, -50)); @@ -453,7 +451,7 @@ void main() { // CAB segment from km 0.6 to 0.9 await tester.dragUntilVisible(find.text('BAB1'), scrollableFinder, const Offset(0, -50)); - final segment4CABStart = findDASTableRowByText('0.6').last; + final segment4CABStart = findDASTableRowByText('0.6').first; final segment4CABStartIcon = find.descendant(of: segment4CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); expect(segment4CABStartIcon, findsOneWidget); final segment4CABStop = findDASTableRowByText('0.9').last; From d868b47551cdb0dfb8c859d2c596c97cbb16b63e Mon Sep 17 00:00:00 2001 From: u221638 Date: Wed, 11 Dec 2024 11:14:48 +0100 Subject: [PATCH 07/22] chore: added train journey to test non standard track equipment. --- .../src/mapper/track_equipment_mapper.dart | 4 +- .../test/sfera/mapper/sfera_mapper_test.dart | 185 ++++++++---------- das_client/test_resources/jp/SFERA_JP_T1.xml | 142 ++++++++++++++ .../test_resources/sp/SFERA_SP_9999_1.xml | 7 - .../test_resources/sp/SFERA_SP_9999_2.xml | 11 +- .../test_resources/sp/SFERA_SP_9999_5.xml | 11 -- .../test_resources/sp/SFERA_SP_T1_1.xml | 108 ++++++++++ .../test_resources/sp/SFERA_SP_T1_2.xml | 119 +++++++++++ .../test_resources/sp/SFERA_SP_T1_3.xml | 101 ++++++++++ .../test_resources/sp/SFERA_SP_T1_4.xml | 111 +++++++++++ .../test_resources/sp/SFERA_SP_T1_5.xml | 138 +++++++++++++ 11 files changed, 811 insertions(+), 126 deletions(-) create mode 100644 das_client/test_resources/jp/SFERA_JP_T1.xml create mode 100644 das_client/test_resources/sp/SFERA_SP_T1_1.xml create mode 100644 das_client/test_resources/sp/SFERA_SP_T1_2.xml create mode 100644 das_client/test_resources/sp/SFERA_SP_T1_3.xml create mode 100644 das_client/test_resources/sp/SFERA_SP_T1_4.xml create mode 100644 das_client/test_resources/sp/SFERA_SP_T1_5.xml diff --git a/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart b/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart index ab41183d..c7a89a75 100644 --- a/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart +++ b/das_client/lib/sfera/src/mapper/track_equipment_mapper.dart @@ -71,8 +71,8 @@ class TrackEquipmentMapper { final type = start?.type ?? end?.type; return NonStandardTrackEquipmentSegment( type: type!.trackEquipmentType, - startOrder: start != null ? SferaModelMapper.calculateOrder(start.segmentIndex, start.startLocation!) : null, - endOrder: end != null ? SferaModelMapper.calculateOrder(end.segmentIndex, end.endLocation!) : null, + startOrder: start?.startLocation != null ? SferaModelMapper.calculateOrder(start!.segmentIndex, start.startLocation!) : null, + endOrder: end?.endLocation != null ? SferaModelMapper.calculateOrder(end!.segmentIndex, end.endLocation!) : null, startKm: start?.startKm ?? [], endKm: end?.endKm ?? [], ); diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart index 0680d914..70b8b711 100644 --- a/das_client/test/sfera/mapper/sfera_mapper_test.dart +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -70,156 +70,141 @@ void main() { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(30)); + expect(journey.data, hasLength(24)); // segment 1 expect(journey.data[0], TypeMatcher()); expect(journey.data[1], TypeMatcher()); - expect(journey.data[2], TypeMatcher()); - expect(journey.data[3], TypeMatcher()); - expect(journey.data[4], TypeMatcher()); - expect(journey.data[5], TypeMatcher()); + expect(journey.data[2], TypeMatcher()); + expect(journey.data[3], TypeMatcher()); + expect(journey.data[4], TypeMatcher()); // segment 2 - expect(journey.data[6], TypeMatcher()); - expect(journey.data[7], TypeMatcher()); - expect(journey.data[8], TypeMatcher()); - expect(journey.data[9], TypeMatcher()); - expect(journey.data[10], TypeMatcher()); - expect(journey.data[11], TypeMatcher()); - expect(journey.data[12], TypeMatcher()); + expect(journey.data[5], TypeMatcher()); + expect(journey.data[6], TypeMatcher()); + expect(journey.data[7], TypeMatcher()); + expect(journey.data[8], TypeMatcher()); + expect(journey.data[9], TypeMatcher()); + expect(journey.data[10], TypeMatcher()); // segment 3 - expect(journey.data[13], TypeMatcher()); + expect(journey.data[11], TypeMatcher()); + expect(journey.data[12], TypeMatcher()); + expect(journey.data[13], TypeMatcher()); expect(journey.data[14], TypeMatcher()); - expect(journey.data[15], TypeMatcher()); - expect(journey.data[16], TypeMatcher()); - expect(journey.data[17], TypeMatcher()); - expect(journey.data[18], TypeMatcher()); + expect(journey.data[15], TypeMatcher()); // segment 4 + expect(journey.data[16], TypeMatcher()); + expect(journey.data[17], TypeMatcher()); + expect(journey.data[18], TypeMatcher()); expect(journey.data[19], TypeMatcher()); - expect(journey.data[20], TypeMatcher()); - expect(journey.data[21], TypeMatcher()); - expect(journey.data[22], TypeMatcher()); - expect(journey.data[23], TypeMatcher()); + expect(journey.data[20], TypeMatcher()); // segment 5 - expect(journey.data[24], TypeMatcher()); - expect(journey.data[25], TypeMatcher()); - expect(journey.data[26], TypeMatcher()); - expect(journey.data[27], TypeMatcher()); - expect(journey.data[28], TypeMatcher()); - expect(journey.data[29], TypeMatcher()); + expect(journey.data[21], TypeMatcher()); + expect(journey.data[22], TypeMatcher()); + expect(journey.data[23], TypeMatcher()); }); test('Test kilometre are parsed correctly', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(30)); + expect(journey.data, hasLength(24)); // segment 1 expect(journey.data[0].kilometre[0], 0.2); expect(journey.data[1].kilometre[0], 0.5); - expect(journey.data[2].kilometre[0], 0.55); - expect(journey.data[3].kilometre[0], 0.6); - expect(journey.data[4].kilometre[0], 0.7); - expect(journey.data[5].kilometre[0], 0.8); + expect(journey.data[2].kilometre[0], 0.6); + expect(journey.data[3].kilometre[0], 0.7); + expect(journey.data[4].kilometre[0], 0.8); // segment 2 - expect(journey.data[6].kilometre[0], 1.1); - expect(journey.data[7].kilometre[0], 1.2); - expect(journey.data[8].kilometre[0], 1.5); - expect(journey.data[9].kilometre[0], 1.5); - expect(journey.data[10].kilometre[0], 1.7); - expect(journey.data[11].kilometre[0], 1.7); - expect(journey.data[12].kilometre[0], 1.8); - expect(journey.data[13].kilometre[0], 1.9); + expect(journey.data[5].kilometre[0], 1.2); + expect(journey.data[6].kilometre[0], 1.2); + expect(journey.data[7].kilometre[0], 1.5); + expect(journey.data[8].kilometre[0], 1.7); + expect(journey.data[9].kilometre[0], 1.8); + expect(journey.data[10].kilometre[0], 1.9); // segment 3 - expect(journey.data[14].kilometre[0], 2.1); - expect(journey.data[15].kilometre[0], 2.2); - expect(journey.data[16].kilometre[0], 2.4); - expect(journey.data[17].kilometre[0], 2.5); - expect(journey.data[18].kilometre[0], 2.6); + expect(journey.data[11].kilometre[0], 2.1); + expect(journey.data[12].kilometre[0], 2.2); + expect(journey.data[13].kilometre[0], 2.4); + expect(journey.data[14].kilometre[0], 2.5); + expect(journey.data[15].kilometre[0], 2.6); // segment 4 - expect(journey.data[19].kilometre[0], 3.5); - expect(journey.data[20].kilometre[0], 3.5); - expect(journey.data[21].kilometre[0], 3.7); - expect(journey.data[21].kilometre[1], 0); - expect(journey.data[22].kilometre[0], 0.1); - expect(journey.data[23].kilometre[0], 0.2); + expect(journey.data[16].kilometre[0], 3.5); + expect(journey.data[17].kilometre[0], 3.5); + expect(journey.data[18].kilometre[0], 3.7); + expect(journey.data[18].kilometre[1], 0); + expect(journey.data[19].kilometre[0], 0.1); + expect(journey.data[20].kilometre[0], 0.2); // segment 5 - expect(journey.data[24].kilometre[0], 0.6); - expect(journey.data[25].kilometre[0], 0.6); - expect(journey.data[26].kilometre[0], 0.9); - expect(journey.data[27].kilometre[0], 0.9); - expect(journey.data[28].kilometre[0], 1.0); - expect(journey.data[29].kilometre[0], 1.1); + expect(journey.data[21].kilometre[0], 0.6); + expect(journey.data[22].kilometre[0], 0.9); + expect(journey.data[23].kilometre[0], 1.1); }); test('Test order is generated correctly', () async { final journey = getJourney('9999', 5); expect(journey.valid, true); - expect(journey.data, hasLength(30)); + expect(journey.data, hasLength(24)); // segment 1 expect(journey.data[0].order, 000200); expect(journey.data[1].order, 000500); - expect(journey.data[2].order, 000550); - expect(journey.data[3].order, 000600); - expect(journey.data[4].order, 000700); - expect(journey.data[5].order, 000800); + expect(journey.data[2].order, 000600); + expect(journey.data[3].order, 000700); + expect(journey.data[4].order, 000800); // segment 2 - expect(journey.data[6].order, 100100); - expect(journey.data[7].order, 100200); - expect(journey.data[8].order, 100500); - expect(journey.data[9].order, 100500); - expect(journey.data[10].order, 100700); - expect(journey.data[11].order, 100700); - expect(journey.data[12].order, 100800); - expect(journey.data[13].order, 100900); + expect(journey.data[5].order, 100200); + expect(journey.data[6].order, 100200); + expect(journey.data[7].order, 100500); + expect(journey.data[8].order, 100700); + expect(journey.data[9].order, 100800); + expect(journey.data[10].order, 100900); // segment 3 - expect(journey.data[14].order, 200100); - expect(journey.data[15].order, 200200); - expect(journey.data[16].order, 200400); - expect(journey.data[17].order, 200500); - expect(journey.data[18].order, 200600); + expect(journey.data[11].order, 200100); + expect(journey.data[12].order, 200200); + expect(journey.data[13].order, 200400); + expect(journey.data[14].order, 200500); + expect(journey.data[15].order, 200600); // segment 4 - expect(journey.data[19].order, 300500); - expect(journey.data[20].order, 300500); - expect(journey.data[21].order, 300700); - expect(journey.data[22].order, 300800); - expect(journey.data[23].order, 300900); + expect(journey.data[16].order, 300500); + expect(journey.data[17].order, 300500); + expect(journey.data[18].order, 300700); + expect(journey.data[19].order, 300800); + expect(journey.data[20].order, 300900); // segment 5 - expect(journey.data[24].order, 400300); - expect(journey.data[25].order, 400300); - expect(journey.data[26].order, 400600); - expect(journey.data[27].order, 400600); - expect(journey.data[28].order, 400700); - expect(journey.data[29].order, 400800); + expect(journey.data[21].order, 400300); + expect(journey.data[22].order, 400600); + expect(journey.data[23].order, 400800); }); test('Test track equipment is generated correctly', () async { - final journey = getJourney('9999', 5); + final journey = getJourney('T1', 5); expect(journey.valid, true); - expect(journey.metadata.nonStandardTrackEquipmentSegments, hasLength(6)); + expect(journey.metadata.nonStandardTrackEquipmentSegments, hasLength(7)); expect(journey.metadata.nonStandardTrackEquipmentSegments[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); expect(journey.metadata.nonStandardTrackEquipmentSegments[0].startOrder, isNull); - expect(journey.metadata.nonStandardTrackEquipmentSegments[0].endOrder, 000550); + expect(journey.metadata.nonStandardTrackEquipmentSegments[0].endOrder, 1500); expect(journey.metadata.nonStandardTrackEquipmentSegments[1].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); - expect(journey.metadata.nonStandardTrackEquipmentSegments[1].startOrder, 000600); - expect(journey.metadata.nonStandardTrackEquipmentSegments[1].endOrder, 000900); + expect(journey.metadata.nonStandardTrackEquipmentSegments[1].startOrder, isNull); + expect(journey.metadata.nonStandardTrackEquipmentSegments[1].endOrder, 102300); expect(journey.metadata.nonStandardTrackEquipmentSegments[2].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); - expect(journey.metadata.nonStandardTrackEquipmentSegments[2].startOrder, 100100); - expect(journey.metadata.nonStandardTrackEquipmentSegments[2].endOrder, 100500); + expect(journey.metadata.nonStandardTrackEquipmentSegments[2].startOrder, 102500); + expect(journey.metadata.nonStandardTrackEquipmentSegments[2].endOrder, 103700); expect(journey.metadata.nonStandardTrackEquipmentSegments[3].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); - expect(journey.metadata.nonStandardTrackEquipmentSegments[3].startOrder, 100700); - expect(journey.metadata.nonStandardTrackEquipmentSegments[3].endOrder, 300500); - expect(journey.metadata.nonStandardTrackEquipmentSegments[4].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); - expect(journey.metadata.nonStandardTrackEquipmentSegments[4].startOrder, 400300); - expect(journey.metadata.nonStandardTrackEquipmentSegments[4].endOrder, 400600); + expect(journey.metadata.nonStandardTrackEquipmentSegments[3].startOrder, 103700); + expect(journey.metadata.nonStandardTrackEquipmentSegments[3].endOrder, 307000); + expect(journey.metadata.nonStandardTrackEquipmentSegments[4].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[4].startOrder, 307000); + expect(journey.metadata.nonStandardTrackEquipmentSegments[4].endOrder, 307800); expect(journey.metadata.nonStandardTrackEquipmentSegments[5].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); - expect(journey.metadata.nonStandardTrackEquipmentSegments[5].startOrder, 400700); - expect(journey.metadata.nonStandardTrackEquipmentSegments[5].endOrder, isNull); + expect(journey.metadata.nonStandardTrackEquipmentSegments[5].startOrder, 409200); + expect(journey.metadata.nonStandardTrackEquipmentSegments[5].endOrder, 410200); + expect(journey.metadata.nonStandardTrackEquipmentSegments[6].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[6].startOrder, 410200); + expect(journey.metadata.nonStandardTrackEquipmentSegments[6].endOrder, isNull); }); test('Test signals are generated correctly', () async { diff --git a/das_client/test_resources/jp/SFERA_JP_T1.xml b/das_client/test_resources/jp/SFERA_JP_T1.xml new file mode 100644 index 00000000..cb3ee0d8 --- /dev/null +++ b/das_client/test_resources/jp/SFERA_JP_T1.xml @@ -0,0 +1,142 @@ + + + + + 1085 + T1 + 2024-12-11 + + + + + 0085 + + + + + + + + + + + + + + + + + + + + + + + + + 0085 + + + + + + + + + + + + + + + + + + + + + + + + + 0085 + + + + + + + + + + + + + + + + + + + + + + + + + 0085 + + + + + + + + + + + + + + + + + + + + + + + + + 0085 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_1.xml b/das_client/test_resources/sp/SFERA_SP_9999_1.xml index e3a0f659..9fe83e57 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_1.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_1.xml @@ -68,10 +68,6 @@ - - - - @@ -85,9 +81,6 @@ - - - diff --git a/das_client/test_resources/sp/SFERA_SP_9999_2.xml b/das_client/test_resources/sp/SFERA_SP_9999_2.xml index 6f2821b0..96738c76 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_2.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_2.xml @@ -14,7 +14,6 @@ - entry @@ -45,10 +44,10 @@ - + - + @@ -56,15 +55,15 @@ - - - + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_5.xml b/das_client/test_resources/sp/SFERA_SP_9999_5.xml index 3350c7b0..5d5e89f1 100644 --- a/das_client/test_resources/sp/SFERA_SP_9999_5.xml +++ b/das_client/test_resources/sp/SFERA_SP_9999_5.xml @@ -49,14 +49,6 @@ - - - - - - - - @@ -66,9 +58,6 @@ - - - diff --git a/das_client/test_resources/sp/SFERA_SP_T1_1.xml b/das_client/test_resources/sp/SFERA_SP_T1_1.xml new file mode 100644 index 00000000..5fd3c31f --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_T1_1.xml @@ -0,0 +1,108 @@ + + + + 0085 + + + + + + CH + 3002 + + + + + + + CH + 3003 + + + + + + + CH + 3004 + + + + + + + CH + 3005 + + + + + + + + CH + 3002 + + + + + + + CH + 3003 + + + + + + + CH + 3004 + + + + + + + CH + 3005 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_T1_2.xml b/das_client/test_resources/sp/SFERA_SP_T1_2.xml new file mode 100644 index 00000000..c1a0b525 --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_T1_2.xml @@ -0,0 +1,119 @@ + + + + 0085 + + + + + + CH + 3006 + + + + + + + CH + 3007 + + + + + + + CH + 3008 + + + + + + + CH + 3009 + + + + + + + + CH + 3006 + + + + + + + CH + 3007 + + + + + + + CH + 3008 + + + + + + + CH + 3009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_T1_3.xml b/das_client/test_resources/sp/SFERA_SP_T1_3.xml new file mode 100644 index 00000000..88b85ebe --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_T1_3.xml @@ -0,0 +1,101 @@ + + + + 0085 + + + + + + CH + 3010 + + + + + + + CH + 3011 + + + + + + + CH + 3012 + + + + + + + CH + 3013 + + + + + + + CH + 3010 + + + + + + + CH + 3011 + + + + + + + CH + 3012 + + + + + + + CH + 3013 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_T1_4.xml b/das_client/test_resources/sp/SFERA_SP_T1_4.xml new file mode 100644 index 00000000..f719eef5 --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_T1_4.xml @@ -0,0 +1,111 @@ + + + + 0085 + + + + + + CH + 3014 + + + + + + + CH + 3015 + + + + + + + CH + 3016 + + + + + + + CH + 3017 + + + + + + + CH + 3014 + + + + + + + CH + 3015 + + + + + + + CH + 3016 + + + + + + + CH + 3017 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_T1_5.xml b/das_client/test_resources/sp/SFERA_SP_T1_5.xml new file mode 100644 index 00000000..09f35262 --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_T1_5.xml @@ -0,0 +1,138 @@ + + + + 0085 + + + + + + CH + 3018 + + + + + + + CH + 3019 + + + + + + + CH + 3020 + + + + + + + CH + 3021 + + + + + + + CH + 3022 + + + + + + + CH + 3018 + + + + + + + CH + 3019 + + + + + + + CH + 3020 + + + + + + + CH + 3021 + + + + + + + CH + 3022 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 638c975c554b91a24a858aab7e2a0b8d3d0af578 Mon Sep 17 00:00:00 2001 From: u221638 Date: Thu, 12 Dec 2024 09:41:19 +0100 Subject: [PATCH 08/22] chore: updated integration test for track equipment to T1 train journey --- .../test/train_journey_table_test.dart | 63 +++++++++---------- .../test_resources/sp/SFERA_SP_T1_5.xml | 2 +- .../sp/SFERA_SP_T1_5.xml | 2 +- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/das_client/integration_test/test/train_journey_table_test.dart b/das_client/integration_test/test/train_journey_table_test.dart index 1599a90b..a8c8fa90 100644 --- a/das_client/integration_test/test/train_journey_table_test.dart +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -424,51 +424,46 @@ void main() { await prepareAndStartApp(tester); // load train journey by filling out train selection page - await _loadTrainJourney(tester, trainNumber: '9999'); + await _loadTrainJourney(tester, trainNumber: 'T1'); final scrollableFinder = find.byType(ListView); expect(scrollableFinder, findsOneWidget); - // CAB segment with start outside train journey and end at 0.6 km - await tester.dragUntilVisible(find.text('0.6').first, scrollableFinder, const Offset(0, -50)); - final segment1CABStop = findDASTableRowByText('0.6').first; + // CAB segment with start outside train journey and end at 33.8 km + await tester.dragUntilVisible(find.text('29.7').first, scrollableFinder, const Offset(0, -50)); + final rowsAtKm33_8 = findDASTableRowByText('33.8'); + expect(rowsAtKm33_8, findsExactly(2)); + final segment1CABStop = rowsAtKm33_8.last; // end should be after other elements at same location final segment1CABStopIcon = find.descendant(of: segment1CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); expect(segment1CABStopIcon, findsOneWidget); - // CAB segment from km 1.1 to 1.5 - await tester.dragUntilVisible(find.text('1.1'), scrollableFinder, const Offset(0, -50)); - final segment2CABStart = findDASTableRowByText('1.1'); + // Track equipment segment without ETCS level 2 should be ignored + await tester.dragUntilVisible(find.text('12.5').first, scrollableFinder, const Offset(0, -50)); + final etcsL1LSEnd = findDASTableRowByText('10.1'); + expect(etcsL1LSEnd, findsNothing); + + // CAB segment between km 12.5 - km 39.9 + await tester.dragUntilVisible(find.text('39.1').first, scrollableFinder, const Offset(0, -50)); + final rowsAtKm12_5 = findDASTableRowByText('12.5'); + expect(rowsAtKm12_5, findsExactly(2)); + final segment2CABStart = rowsAtKm12_5.first; // start should be before other elements at same location final segment2CABStartIcon = find.descendant(of: segment2CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); expect(segment2CABStartIcon, findsOneWidget); - await tester.dragUntilVisible(find.text('1.7'), scrollableFinder, const Offset(0, -50)); - final segment2CABStop = findDASTableRowByText('1.5').last; - final segment2CABStopIcon = find.descendant(of: segment2CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); - expect(segment2CABStopIcon, findsOneWidget); - - // CAB segment from km 1.7 to 3.5 - await tester.dragUntilVisible(find.text('1.8'), scrollableFinder, const Offset(0, -50)); - final segment3CABStart = findDASTableRowByText('1.7').first; + await tester.dragUntilVisible(find.text('75.3'), scrollableFinder, const Offset(0, -50)); + final trackEquipmentTypeChange = findDASTableRowByText('56.8'); + expect(trackEquipmentTypeChange, findsNothing); // no CAB signaling at connecting ETCS L2 segments + await tester.dragUntilVisible(find.text('41.5'), scrollableFinder, const Offset(0, -50)); + final rothristServicePointRow = findDASTableRowByText('46.2'); + expect(rothristServicePointRow, findsOneWidget); // no CAB signaling at connecting ETCS L2 segments + final segment2CABEnd = findDASTableRowByText('39.9'); + final segment2CABEndIcon = find.descendant(of: segment2CABEnd, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); + expect(segment2CABEndIcon, findsOneWidget); + + // CAB segment with end outside train journey and start at 8.3 km + await tester.dragUntilVisible(find.text('9.5'), scrollableFinder, const Offset(0, -50)); + final segment3CABStart = findDASTableRowByText('8.3'); final segment3CABStartIcon = find.descendant(of: segment3CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); expect(segment3CABStartIcon, findsOneWidget); - await tester.dragUntilVisible(find.text('3.7'), scrollableFinder, const Offset(0, -50)); - final segment3CABStop = findDASTableRowByText('3.5').last; - final segment3CABStopIcon = find.descendant(of: segment3CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); - expect(segment3CABStopIcon, findsOneWidget); - - // CAB segment from km 0.6 to 0.9 - await tester.dragUntilVisible(find.text('BAB1'), scrollableFinder, const Offset(0, -50)); - final segment4CABStart = findDASTableRowByText('0.6').first; - final segment4CABStartIcon = find.descendant(of: segment4CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); - expect(segment4CABStartIcon, findsOneWidget); - final segment4CABStop = findDASTableRowByText('0.9').last; - final segment4CABStopIcon = find.descendant(of: segment4CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); - expect(segment4CABStopIcon, findsOneWidget); - - // CAB segment with end outside train journey and start at 1.0 km - await tester.dragUntilVisible(find.text('1.0'), scrollableFinder, const Offset(0, -50)); - final segment5CABStart = findDASTableRowByText('1.0').first; - final segment5CABStartIcon = find.descendant(of: segment5CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); - expect(segment5CABStartIcon, findsOneWidget); }); }); } diff --git a/das_client/test_resources/sp/SFERA_SP_T1_5.xml b/das_client/test_resources/sp/SFERA_SP_T1_5.xml index 09f35262..188eca9f 100644 --- a/das_client/test_resources/sp/SFERA_SP_T1_5.xml +++ b/das_client/test_resources/sp/SFERA_SP_T1_5.xml @@ -120,7 +120,7 @@ - + diff --git a/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_T1_5.xml b/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_T1_5.xml index 09f35262..188eca9f 100644 --- a/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_T1_5.xml +++ b/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_T1_5.xml @@ -120,7 +120,7 @@ - + From 2fe61eac2b3e72c6c5fb9b1de83f5b0ec4e7f272 Mon Sep 17 00:00:00 2001 From: u221711 Date: Thu, 12 Dec 2024 11:18:58 +0100 Subject: [PATCH 09/22] fix ui tests --- .../integration_test/test/train_journey_table_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/das_client/integration_test/test/train_journey_table_test.dart b/das_client/integration_test/test/train_journey_table_test.dart index a8c8fa90..20580f9a 100644 --- a/das_client/integration_test/test/train_journey_table_test.dart +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -151,8 +151,11 @@ void main() { expect(scrollableFinder, findsOneWidget); final stopRouteRow = findDASTableRowByText('Bahnhof A'); - final nonStoppingPassRouteRow = findDASTableRowByText('Haltestelle B'); expect(stopRouteRow, findsOneWidget); + + await tester.dragUntilVisible(find.text('Haltestelle B'), scrollableFinder, const Offset(0, -50)); + + final nonStoppingPassRouteRow = findDASTableRowByText('Haltestelle B'); expect(nonStoppingPassRouteRow, findsOneWidget); // check stop circles @@ -443,7 +446,6 @@ void main() { expect(etcsL1LSEnd, findsNothing); // CAB segment between km 12.5 - km 39.9 - await tester.dragUntilVisible(find.text('39.1').first, scrollableFinder, const Offset(0, -50)); final rowsAtKm12_5 = findDASTableRowByText('12.5'); expect(rowsAtKm12_5, findsExactly(2)); final segment2CABStart = rowsAtKm12_5.first; // start should be before other elements at same location From 924ea83d2b7dfe9a66e3cb013aea6f7a7186a84e Mon Sep 17 00:00:00 2001 From: u221711 Date: Thu, 12 Dec 2024 11:37:29 +0100 Subject: [PATCH 10/22] fix tests for real --- das_client/integration_test/test/train_journey_table_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/das_client/integration_test/test/train_journey_table_test.dart b/das_client/integration_test/test/train_journey_table_test.dart index 20580f9a..9bf97a06 100644 --- a/das_client/integration_test/test/train_journey_table_test.dart +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -153,7 +153,7 @@ void main() { final stopRouteRow = findDASTableRowByText('Bahnhof A'); expect(stopRouteRow, findsOneWidget); - await tester.dragUntilVisible(find.text('Haltestelle B'), scrollableFinder, const Offset(0, -50)); + await tester.dragUntilVisible(findDASTableRowByText('Haltestelle B'), scrollableFinder, const Offset(0, -50)); final nonStoppingPassRouteRow = findDASTableRowByText('Haltestelle B'); expect(nonStoppingPassRouteRow, findsOneWidget); From 8987de9fb81b8ce4c5ab3e4fa2856bbcd4c64ab7 Mon Sep 17 00:00:00 2001 From: u221638 Date: Tue, 17 Dec 2024 11:03:21 +0100 Subject: [PATCH 11/22] feat: added visualization of non standard track equipment (#82). --- .../test/train_journey_table_test.dart | 6 +- .../additional_speed_restriction_row.dart | 3 +- .../widgets/table/base_row_builder.dart | 16 ++ .../widgets/table/cab_signaling_row.dart | 1 + ...dy.dart => bracket_station_cell_body.dart} | 11 +- .../widgets/table/cells/route_cell_body.dart | 14 +- .../cells/track_equipment_cell_body.dart | 203 ++++++++++++++++++ .../cells/track_equipment_render_data.dart | 106 +++++++++ .../widgets/table/connection_track_row.dart | 1 + .../widgets/table/curve_point_row.dart | 1 + .../widgets/table/protection_section_row.dart | 3 +- .../widgets/table/service_point_row.dart | 20 +- .../widgets/table/signal_row.dart | 1 + .../widgets/table/speed_change_row.dart | 1 + .../train_journey/widgets/train_journey.dart | 56 ++++- .../lib/model/journey/cab_signaling.dart | 2 + .../lib/model/journey/track_equipment.dart | 11 + 17 files changed, 424 insertions(+), 32 deletions(-) rename das_client/lib/app/pages/journey/train_journey/widgets/table/cells/{bracket_station_body.dart => bracket_station_cell_body.dart} (80%) create mode 100644 das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart create mode 100644 das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_render_data.dart diff --git a/das_client/integration_test/test/train_journey_table_test.dart b/das_client/integration_test/test/train_journey_table_test.dart index 9bf97a06..8cd3766f 100644 --- a/das_client/integration_test/test/train_journey_table_test.dart +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -1,6 +1,6 @@ import 'package:das_client/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart'; -import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/bracket_station_cell_body.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/curve_point_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/protection_section_row.dart'; @@ -312,9 +312,9 @@ void main() { // check if the bracket station widget is displayed final bracketStationDWidget = - find.descendant(of: bracketStationD, matching: find.byKey(BracketStationBody.bracketStationKey)); + find.descendant(of: bracketStationD, matching: find.byKey(BracketStationCellBody.bracketStationKey)); final bracketStationD1Widget = - find.descendant(of: bracketStationD1, matching: find.byKey(BracketStationBody.bracketStationKey)); + find.descendant(of: bracketStationD1, matching: find.byKey(BracketStationCellBody.bracketStationKey)); expect(bracketStationDWidget, findsOneWidget); expect(bracketStationD1Widget, findsOneWidget); diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart index cab347ef..455aa662 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart @@ -10,12 +10,11 @@ import 'package:flutter_svg/flutter_svg.dart'; class AdditionalSpeedRestrictionRow extends BaseRowBuilder { static const Key additionalSpeedRestrictionIconKey = Key('addition_speed_restriction_icon_key'); static const Color additionalSpeedRestrictionColor = SBBColors.orange; - static const double rowHeight = 44.0; AdditionalSpeedRestrictionRow({ required super.metadata, required super.data, - super.height = rowHeight, + super.trackEquipmentRenderData, }) : super(rowColor: additionalSpeedRestrictionColor); @override 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 index 4fb8fcbb..239ebd59 100644 --- 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 @@ -1,5 +1,7 @@ import 'package:das_client/app/pages/journey/train_journey/widgets/table/additional_speed_restriction_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_render_data.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:das_client/model/journey/additional_speed_restriction.dart'; @@ -14,6 +16,7 @@ class BaseRowBuilder extends DASTableRowBuilder { required this.metadata, required this.data, super.height = rowHeight, + this.trackEquipmentRenderData = const TrackEquipmentRenderData(), this.defaultAlignment = Alignment.bottomCenter, this.rowColor, }); @@ -22,6 +25,7 @@ class BaseRowBuilder extends DASTableRowBuilder { final Color? rowColor; final Metadata metadata; final T data; + final TrackEquipmentRenderData trackEquipmentRenderData; @override DASTableRow build(BuildContext context) { @@ -32,6 +36,7 @@ class BaseRowBuilder extends DASTableRowBuilder { kilometreCell(context), timeCell(context), routeCell(context), + trackEquipment(context), iconsCell1(context), informationCell(context), iconsCell2(context), @@ -75,6 +80,17 @@ class BaseRowBuilder extends DASTableRowBuilder { ); } + DASTableCell trackEquipment(BuildContext context) { + return DASTableCell( + color: specialCellColor, + padding: EdgeInsets.all(0.0), + alignment: null, + child: TrackEquipmentCellBody( + renderData: trackEquipmentRenderData, + ), + ); + } + DASTableCell timeCell(BuildContext context) { return DASTableCell.empty(color: specialCellColor); } diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart index 2a407c32..9751a141 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart @@ -12,6 +12,7 @@ class CABSignalingRow extends BaseRowBuilder { CABSignalingRow({ required super.metadata, required super.data, + super.trackEquipmentRenderData, }); @override diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_cell_body.dart similarity index 80% rename from das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart rename to das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_cell_body.dart index 78ca13d8..382db450 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_cell_body.dart @@ -2,12 +2,12 @@ import 'package:das_client/model/journey/bracket_station.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; -class BracketStationBody extends StatelessWidget { +class BracketStationCellBody extends StatelessWidget { static const Key bracketStationKey = Key('bracketStationKey'); static const double _bracketStationWidth = 16.0; static const double _bracketStationFontSize = 12.0; - const BracketStationBody({ + const BracketStationCellBody({ required this.bracketStation, required this.height, super.key, @@ -34,9 +34,10 @@ class BracketStationBody extends StatelessWidget { child: Text( bracketStation.mainStationAbbreviation ?? '', style: SBBTextStyles.extraSmallBold.copyWith( - color: SBBColors.white, - fontSize: _bracketStationFontSize, - height: _bracketStationWidth / _bracketStationFontSize), + color: SBBColors.white, + fontSize: _bracketStationFontSize, + height: _bracketStationWidth / _bracketStationFontSize, + ), ), ), ), 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 index f98fd752..eb11e0e3 100644 --- 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 @@ -37,11 +37,12 @@ class RouteCellBody extends StatelessWidget { return LayoutBuilder( builder: (context, constraints) { final height = constraints.maxHeight; + final width = constraints.maxWidth; return Stack( clipBehavior: Clip.none, alignment: Alignment.center, children: [ - _routeLine(context, height), + _routeLine(context, height, width), if (isCurrentPosition) _chevron(context), if (isStop) _circle(context), ], @@ -50,15 +51,16 @@ class RouteCellBody extends StatelessWidget { ); } - Positioned _routeLine(BuildContext context, double height) { + Widget _routeLine(BuildContext context, double height, double width) { final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; final lineColor = isDarkTheme ? SBBColors.white : SBBColors.black; + final horizontalBorderWidth = + DASTableTheme.of(context)?.data.tableBorder?.horizontalInside.width ?? sbbDefaultSpacing; return Positioned( key: _routeKey(), - top: isRouteStart ? height - sbbDefaultSpacing : -sbbDefaultSpacing, - bottom: isRouteEnd ? sbbDefaultSpacing : -sbbDefaultSpacing, - right: 0, - left: 0, + top: isRouteStart ? height - sbbDefaultSpacing : 0, + bottom: isRouteEnd ? sbbDefaultSpacing : -horizontalBorderWidth, + left: (width / 2) - (lineThickness / 2), child: VerticalDivider(thickness: lineThickness, color: lineColor), ); } diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart new file mode 100644 index 00000000..384e4e76 --- /dev/null +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart @@ -0,0 +1,203 @@ +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_render_data.dart'; +import 'package:das_client/app/widgets/table/das_table_theme.dart'; +import 'package:das_client/model/journey/track_equipment.dart'; +import 'package:flutter/material.dart'; + +class TrackEquipmentCellBody extends StatelessWidget { + static const Key conventionalExtendedSpeedBorderKey = Key('conventional_extended_speed_border_key'); + static const Key extendedSpeedReversingPossibleKey = Key('extended_speed_reversing_possible_key'); + static const Key extendedSpeedReversingImpossibleKey = Key('extended_speed_reversing_impossible_key'); + static const Key conventionalSpeedReversingImpossible = Key('conventional_speed_reversing_impossible_key'); + + const TrackEquipmentCellBody({ + this.renderData = const TrackEquipmentRenderData(), + super.key, + }); + + final TrackEquipmentRenderData renderData; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final height = constraints.maxHeight; + final trackEquipmentType = renderData.trackEquipmentType; + return Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + if (renderData.isConventionalExtendedSpeedBorder) _conventionalExtendedSpeedBorder(context), + if (trackEquipmentType == TrackEquipmentType.etcsL2ExtSpeedReversingPossible) + _extSpeedReversingPossible(context, height), + if (trackEquipmentType == TrackEquipmentType.etcsL2ExtSpeedReversingImpossible) + _extSpeedReversingImpossible(context, height), + if (trackEquipmentType == TrackEquipmentType.etcsL2ConvSpeedReversingImpossible) + _convSpeedReversingImpossible(context, height), + ], + ); + }, + ); + } + + Widget _extSpeedReversingPossible(BuildContext context, double height) { + return Positioned( + key: extendedSpeedReversingPossibleKey, + top: _calculateTop(height), + bottom: _calculateBottom(context, height), + left: 2.0, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _extSpeedLine(), + SizedBox(width: 2.0), + _extSpeedLine(), + ], + ), + ); + } + + Widget _conventionalExtendedSpeedBorder(BuildContext context) { + return Positioned( + key: conventionalExtendedSpeedBorderKey, + top: 0.0, + left: 0.0, + child: CustomPaint( + painter: _ConventionalExtendedSpeedBorderPainter(), + ), + ); + } + + Widget _extSpeedReversingImpossible(BuildContext context, double height) { + return Positioned( + key: extendedSpeedReversingImpossibleKey, + top: _calculateTop(height), + bottom: _calculateBottom(context, height), + left: 2.0, + child: _extSpeedLine(), + ); + } + + CustomPaint _extSpeedLine() { + final width = 3.0; + return CustomPaint( + painter: _CumulativeDashedLinePainter( + cumulativeHeight: renderData.cumulativeHeight, + dashHeights: [7.0], + dashSpace: 5.0, + width: width, + ), + child: SizedBox(height: double.infinity, width: width), + ); + } + + Widget _convSpeedReversingImpossible(BuildContext context, double height) { + final width = 3.0; + return Positioned( + key: TrackEquipmentCellBody.conventionalSpeedReversingImpossible, + top: _calculateTop(height), + bottom: _calculateBottom(context, height), + left: 2.0, + child: CustomPaint( + painter: _CumulativeDashedLinePainter( + cumulativeHeight: renderData.cumulativeHeight, + dashHeights: [3.0, 7.0], + dashSpace: 5.0, + width: width, + ), + child: SizedBox(height: double.infinity, width: width), + ), + ); + } + + /// Calculation of bottom is used to draw over table border if necessary + double _calculateBottom(BuildContext context, double height) { + if (renderData.isCABEnd) return height / 2; + + final tableBorder = DASTableTheme.of(context)?.data.tableBorder; + return -(tableBorder?.horizontalInside.width ?? 0); + } + + double _calculateTop(double height) { + if (renderData.isCABStart) return height / 2; + + return renderData.isConventionalExtendedSpeedBorder ? conventionalExtendedSpeedBorderSpace : 0; + } + + static double get conventionalExtendedSpeedBorderSpace => 5.0 + _ConventionalExtendedSpeedBorderPainter.height; +} + +class _ConventionalExtendedSpeedBorderPainter extends CustomPainter { + static const double height = 3.0; + static const double width = 10.0; + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.black + ..style = PaintingStyle.fill; + + final rect = Rect.fromLTWH(0, 0, width, height); + canvas.drawRect(rect, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} + +class _CumulativeDashedLinePainter extends CustomPainter { + _CumulativeDashedLinePainter({ + required this.cumulativeHeight, + this.dashHeights = const [4.0], + this.dashSpace = 4.0, + this.width = 3.0, + }) : assert(dashHeights.isNotEmpty); + + final double cumulativeHeight; + final List dashHeights; + final double dashSpace; + final double width; + + @override + void paint(Canvas canvas, Size size) { + // Determine the offset in the cycle based on the cumulative height + final dashPatternLength = dashHeights.reduce((a, b) => a + b) + (dashSpace * (dashHeights.length)); + final offsetInPattern = cumulativeHeight % dashPatternLength; + + // Determine the starting dash index based on the offset + int dashIndex = 0; + double accumulatedLength = 0; + for (int i = 0; i < dashHeights.length; i++) { + accumulatedLength += dashHeights[i] + dashSpace; + if (accumulatedLength > offsetInPattern) { + dashIndex = i + 1; + break; + } + } + + final paint = Paint() + ..color = Colors.black + ..strokeWidth = width; + + double startY = -offsetInPattern; + while (startY < size.height) { + dashIndex = dashIndex % dashHeights.length; + final endY = startY + dashHeights[dashIndex]; + if (endY > 0) { + canvas.drawLine( + Offset(size.width / 2, startY), + Offset(size.width / 2, endY), + paint, + ); + } + startY = endY + dashSpace; + dashIndex++; + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_render_data.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_render_data.dart new file mode 100644 index 00000000..8dfd17c0 --- /dev/null +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_render_data.dart @@ -0,0 +1,106 @@ +import 'package:das_client/app/pages/journey/train_journey/widgets/table/base_row_builder.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/service_point_row.dart'; +import 'package:das_client/model/journey/base_data.dart'; +import 'package:das_client/model/journey/cab_signaling.dart'; +import 'package:das_client/model/journey/datatype.dart'; +import 'package:das_client/model/journey/journey.dart'; +import 'package:das_client/model/journey/track_equipment.dart'; + +/// Data class to hold all the information to visualize the track equipment. +class TrackEquipmentRenderData { + const TrackEquipmentRenderData({ + this.cumulativeHeight = 0.0, + this.isCABStart = false, + this.isCABEnd = false, + this.isConventionalExtendedSpeedBorder = false, + this.trackEquipmentType, + }); + + final double cumulativeHeight; + final bool isCABStart; + final bool isCABEnd; + final bool isConventionalExtendedSpeedBorder; + final TrackEquipmentType? trackEquipmentType; + + factory TrackEquipmentRenderData.from(Journey journey, int index) { + final nonStandardTrackEquipmentSegments = journey.metadata.nonStandardTrackEquipmentSegments; + final trackEquipment = nonStandardTrackEquipmentSegments.appliesToOrder(journey.data[index].order).firstOrNull; + if (trackEquipment == null || !trackEquipment.isEtcsL2Segment) return TrackEquipmentRenderData(); + + return TrackEquipmentRenderData( + trackEquipmentType: trackEquipment.type, + cumulativeHeight: _calculateTrackEquipmentCumulativeHeight(journey, trackEquipment, index), + isConventionalExtendedSpeedBorder: _isConventionalExtendedSpeedBorder(journey, index), + ); + } + + TrackEquipmentRenderData copyWith({ + double? cumulativeHeight, + bool? isCABStart, + bool? isCABEnd, + bool? isConventionalExtendedSpeedBorder, + TrackEquipmentType? trackEquipmentType, + }) => + TrackEquipmentRenderData( + cumulativeHeight: cumulativeHeight ?? this.cumulativeHeight, + isCABStart: isCABStart ?? this.isCABStart, + isCABEnd: isCABEnd ?? this.isCABEnd, + isConventionalExtendedSpeedBorder: isConventionalExtendedSpeedBorder ?? this.isConventionalExtendedSpeedBorder, + trackEquipmentType: trackEquipmentType ?? this.trackEquipmentType, + ); + + /// calculates the cumulative height of the track equipment "line" of previous rows with the same type as given [trackEquipment]. + static double _calculateTrackEquipmentCumulativeHeight( + Journey journey, NonStandardTrackEquipmentSegment trackEquipment, int index) { + var cumulativeHeight = 0.0; + var searchIndex = index - 1; + while (searchIndex >= 0) { + final data = journey.data[searchIndex]; + final testTrackEquipment = + journey.metadata.nonStandardTrackEquipmentSegments.appliesToOrder(data.order).firstOrNull; + + if (testTrackEquipment == null || testTrackEquipment.type != trackEquipment.type) { + break; + } + + cumulativeHeight += _rowHeight(data); + + // if is conventional extended speed border, reduce by it's height as it is not part of the dashed line. + if (_isConventionalExtendedSpeedBorder(journey, searchIndex)) { + cumulativeHeight -= TrackEquipmentCellBody.conventionalExtendedSpeedBorderSpace; + } + + searchIndex--; + } + return cumulativeHeight; + } + + /// returns height of track equipment "line" for given row + static double _rowHeight(BaseData data) { + switch (data.type) { + case Datatype.servicePoint: + return ServicePointRow.rowHeight; + case Datatype.cabSignaling: + return (data as CABSignaling).isStart ? BaseRowBuilder.rowHeight / 2 : BaseRowBuilder.rowHeight; + default: + return BaseRowBuilder.rowHeight; + } + } + + /// checks if between current and previous track equipment is a border between extended and conventional speed. + static bool _isConventionalExtendedSpeedBorder(Journey journey, int index) { + if (index < 1) return false; + + final nonStandardTrackEquipmentSegments = journey.metadata.nonStandardTrackEquipmentSegments; + final currentData = journey.data[index]; + final previousData = journey.data[index - 1]; + + final trackEquipment = nonStandardTrackEquipmentSegments.appliesToOrder(currentData.order).firstOrNull; + final previousTrackEquipment = nonStandardTrackEquipmentSegments.appliesToOrder(previousData.order).firstOrNull; + if (previousTrackEquipment == null || trackEquipment == null) return false; + + return (trackEquipment.isConventionalSpeed && previousTrackEquipment.isExtendedSpeed) || + (trackEquipment.isExtendedSpeed && previousTrackEquipment.isConventionalSpeed); + } +} diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/connection_track_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/connection_track_row.dart index b0df0a83..71e15cdb 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/connection_track_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/connection_track_row.dart @@ -8,6 +8,7 @@ class ConnectionTrackRow extends BaseRowBuilder { ConnectionTrackRow({ required super.metadata, required super.data, + super.trackEquipmentRenderData, }); @override diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/curve_point_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/curve_point_row.dart index 75eb904f..cce422ff 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/curve_point_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/curve_point_row.dart @@ -12,6 +12,7 @@ class CurvePointRow extends BaseRowBuilder { CurvePointRow({ required super.metadata, required super.data, + super.trackEquipmentRenderData, }); @override diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/protection_section_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/protection_section_row.dart index 21387a1d..ec6da544 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/protection_section_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/protection_section_row.dart @@ -9,12 +9,11 @@ import 'package:flutter_svg/flutter_svg.dart'; class ProtectionSectionRow extends BaseRowBuilder { static const Key protectionSectionKey = Key('protection_section_key'); - static const double rowHeight = 44.0; ProtectionSectionRow({ required super.metadata, required super.data, - super.height = rowHeight, + super.trackEquipmentRenderData, }) : super(rowColor: SBBColors.peach); @override 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 index 9ae98616..58a52825 100644 --- 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 @@ -1,6 +1,7 @@ import 'package:das_client/app/pages/journey/train_journey/widgets/table/base_row_builder.dart'; -import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/bracket_station_cell_body.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart'; import 'package:das_client/app/widgets/assets.dart'; import 'package:das_client/app/widgets/table/das_table_cell.dart'; import 'package:das_client/model/journey/service_point.dart'; @@ -17,7 +18,8 @@ class ServicePointRow extends BaseRowBuilder { required super.metadata, required super.data, super.height = rowHeight, - }) : super(rowColor: metadata.nextStop == data ? SBBColors.royal.withOpacity(0.2) : Colors.transparent); + super.trackEquipmentRenderData, + }) : super(rowColor: metadata.nextStop == data ? SBBColors.royal.withOpacity(0.2) : null); @override DASTableCell informationCell(BuildContext context) { @@ -49,7 +51,7 @@ class ServicePointRow extends BaseRowBuilder { clipBehavior: Clip.none, children: [ if (data.bracketStation != null) - BracketStationBody( + BracketStationCellBody( bracketStation: data.bracketStation!, height: height, ), @@ -80,4 +82,16 @@ class ServicePointRow extends BaseRowBuilder { ), ); } + + @override + DASTableCell trackEquipment(BuildContext context) { + return DASTableCell( + color: specialCellColor, + padding: const EdgeInsets.all(0.0), + alignment: null, + child: TrackEquipmentCellBody( + renderData: trackEquipmentRenderData, + ), + ); + } } diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/signal_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/signal_row.dart index e0de615f..48230fef 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/signal_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/signal_row.dart @@ -12,6 +12,7 @@ class SignalRow extends BaseRowBuilder { SignalRow({ required super.metadata, required super.data, + super.trackEquipmentRenderData, }); @override diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/speed_change_row.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/speed_change_row.dart index 141cba44..51ad3d9b 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/speed_change_row.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/speed_change_row.dart @@ -7,6 +7,7 @@ class SpeedChangeRow extends BaseRowBuilder { SpeedChangeRow({ required super.metadata, required super.data, + super.trackEquipmentRenderData, }); @override 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 c6d94c3c..3d77e33b 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,8 +1,9 @@ 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/additional_speed_restriction_row.dart'; -import 'package:das_client/app/pages/journey/train_journey/widgets/table/connection_track_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_render_data.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/connection_track_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/curve_point_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/protection_section_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/service_point_row.dart'; @@ -12,8 +13,8 @@ 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/app/widgets/table/das_table_row.dart'; import 'package:das_client/model/journey/additional_speed_restriction_data.dart'; -import 'package:das_client/model/journey/connection_track.dart'; import 'package:das_client/model/journey/cab_signaling.dart'; +import 'package:das_client/model/journey/connection_track.dart'; import 'package:das_client/model/journey/curve_point.dart'; import 'package:das_client/model/journey/datatype.dart'; import 'package:das_client/model/journey/journey.dart'; @@ -58,27 +59,59 @@ class TrainJourney extends StatelessWidget { return List.generate(journey.data.length, (index) { final rowData = journey.data[index]; + final renderData = TrackEquipmentRenderData.from(journey, index); switch (rowData.type) { case Datatype.servicePoint: - return ServicePointRow(metadata: journey.metadata, data: rowData as ServicePoint).build(context); + return ServicePointRow( + metadata: journey.metadata, + data: rowData as ServicePoint, + trackEquipmentRenderData: renderData, + ).build(context); case Datatype.protectionSection: - return ProtectionSectionRow(metadata: journey.metadata, data: rowData as ProtectionSection).build(context); + return ProtectionSectionRow( + metadata: journey.metadata, + data: rowData as ProtectionSection, + trackEquipmentRenderData: renderData, + ).build(context); case Datatype.curvePoint: - return CurvePointRow(metadata: journey.metadata, data: rowData as CurvePoint).build(context); + return CurvePointRow( + metadata: journey.metadata, + data: rowData as CurvePoint, + trackEquipmentRenderData: renderData, + ).build(context); case Datatype.signal: - return SignalRow(metadata: journey.metadata, data: rowData as Signal).build(context); + return SignalRow( + metadata: journey.metadata, + data: rowData as Signal, + trackEquipmentRenderData: renderData, + ).build(context); case Datatype.additionalSpeedRestriction: return AdditionalSpeedRestrictionRow( - metadata: journey.metadata, data: rowData as AdditionalSpeedRestrictionData) - .build(context); + metadata: journey.metadata, + data: rowData as AdditionalSpeedRestrictionData, + trackEquipmentRenderData: renderData, + ).build(context); case Datatype.connectionTrack: - return ConnectionTrackRow(metadata: journey.metadata, data: rowData as ConnectionTrack).build(context); + return ConnectionTrackRow( + metadata: journey.metadata, + data: rowData as ConnectionTrack, + trackEquipmentRenderData: renderData, + ).build(context); case Datatype.speedChange: - return SpeedChangeRow(metadata: journey.metadata, data: rowData as SpeedChange).build(context); + return SpeedChangeRow( + metadata: journey.metadata, + data: rowData as SpeedChange, + trackEquipmentRenderData: renderData, + ).build(context); case Datatype.cabSignaling: + final data = rowData as CABSignaling; return CABSignalingRow( metadata: journey.metadata, - data: rowData as CABSignaling, + data: data, + trackEquipmentRenderData: renderData.copyWith( + isCABStart: data.isStart, + isCABEnd: data.isEnd, + ), ).build(context); } }); @@ -89,6 +122,7 @@ class TrainJourney extends StatelessWidget { 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: 20.0), // track equipment column DASTableColumn(width: 64.0), // icons column DASTableColumn( child: Text(context.l10n.p_train_journey_table_journey_information_label), diff --git a/das_client/lib/model/journey/cab_signaling.dart b/das_client/lib/model/journey/cab_signaling.dart index ca01c2e5..1aaefaad 100644 --- a/das_client/lib/model/journey/cab_signaling.dart +++ b/das_client/lib/model/journey/cab_signaling.dart @@ -10,6 +10,8 @@ class CABSignaling extends BaseData { final bool isStart; + bool get isEnd => !isStart; + @override int get orderPriority => isStart ? -1 : 1; diff --git a/das_client/lib/model/journey/track_equipment.dart b/das_client/lib/model/journey/track_equipment.dart index 62e1e953..b144fc40 100644 --- a/das_client/lib/model/journey/track_equipment.dart +++ b/das_client/lib/model/journey/track_equipment.dart @@ -22,6 +22,10 @@ class NonStandardTrackEquipmentSegment implements Comparable { bool get isEtcsL2Segment => type.isEtcsL2; + bool get isConventionalSpeed => type.isConventionalSpeed; + + bool get isExtendedSpeed => type.isExtendedSpeed; + /// checks if the given order is part of this segment. bool appliesToOrder(int order) { if (startOrder != null && endOrder != null) { @@ -59,6 +63,13 @@ enum TrackEquipmentType { TrackEquipmentType.etcsL2ExtSpeedReversingPossible, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible ].contains(this); + + bool get isExtendedSpeed => [ + TrackEquipmentType.etcsL2ExtSpeedReversingPossible, + TrackEquipmentType.etcsL2ExtSpeedReversingImpossible + ].contains(this); + + bool get isConventionalSpeed => this == etcsL2ConvSpeedReversingImpossible; } // extensions From 7698872652a233a1f4d6877839fc480948936ed0 Mon Sep 17 00:00:00 2001 From: u221638 Date: Tue, 17 Dec 2024 16:30:35 +0100 Subject: [PATCH 12/22] chore: add integration tests for track equipment --- .../test/train_journey_table_test.dart | 93 +++++++++++++++++-- 1 file changed, 86 insertions(+), 7 deletions(-) diff --git a/das_client/integration_test/test/train_journey_table_test.dart b/das_client/integration_test/test/train_journey_table_test.dart index 8cd3766f..beefec5b 100644 --- a/das_client/integration_test/test/train_journey_table_test.dart +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -2,6 +2,7 @@ import 'package:das_client/app/pages/journey/train_journey/widgets/table/additio import 'package:das_client/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/bracket_station_cell_body.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart'; +import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/curve_point_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/protection_section_row.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/service_point_row.dart'; @@ -82,7 +83,7 @@ void main() { it is Container && it.decoration is BoxDecoration && (it.decoration as BoxDecoration).color == AdditionalSpeedRestrictionRow.additionalSpeedRestrictionColor)); - expect(coloredCells, findsNWidgets(11)); + expect(coloredCells, findsNWidgets(12)); }); testWidgets('test other rows are displayed correctly', (tester) async { @@ -99,7 +100,7 @@ void main() { final testRows = ['Genève', 'km 32.2', 'Lengnau', 'WANZ']; - //Scroll to the table and search inside it + // Scroll to the table and search inside it for (final rowText in testRows) { final rowFinder = find.descendant(of: tableFinder, matching: find.text(rowText)); await tester.dragUntilVisible(rowFinder, tableFinder, const Offset(0, -50)); @@ -115,7 +116,7 @@ void main() { it.decoration is BoxDecoration && (it.decoration as BoxDecoration).color == AdditionalSpeedRestrictionRow.additionalSpeedRestrictionColor)); - expect(coloredCells, findsNWidgets(3)); + expect(coloredCells, findsNWidgets(4)); } }); @@ -437,7 +438,8 @@ void main() { final rowsAtKm33_8 = findDASTableRowByText('33.8'); expect(rowsAtKm33_8, findsExactly(2)); final segment1CABStop = rowsAtKm33_8.last; // end should be after other elements at same location - final segment1CABStopIcon = find.descendant(of: segment1CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); + final segment1CABStopIcon = + find.descendant(of: segment1CABStop, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); expect(segment1CABStopIcon, findsOneWidget); // Track equipment segment without ETCS level 2 should be ignored @@ -449,7 +451,8 @@ void main() { final rowsAtKm12_5 = findDASTableRowByText('12.5'); expect(rowsAtKm12_5, findsExactly(2)); final segment2CABStart = rowsAtKm12_5.first; // start should be before other elements at same location - final segment2CABStartIcon = find.descendant(of: segment2CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); + final segment2CABStartIcon = + find.descendant(of: segment2CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); expect(segment2CABStartIcon, findsOneWidget); await tester.dragUntilVisible(find.text('75.3'), scrollableFinder, const Offset(0, -50)); final trackEquipmentTypeChange = findDASTableRowByText('56.8'); @@ -458,18 +461,94 @@ void main() { final rothristServicePointRow = findDASTableRowByText('46.2'); expect(rothristServicePointRow, findsOneWidget); // no CAB signaling at connecting ETCS L2 segments final segment2CABEnd = findDASTableRowByText('39.9'); - final segment2CABEndIcon = find.descendant(of: segment2CABEnd, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); + final segment2CABEndIcon = + find.descendant(of: segment2CABEnd, matching: find.byKey(CABSignalingRow.cabSignalingEndIconKey)); expect(segment2CABEndIcon, findsOneWidget); // CAB segment with end outside train journey and start at 8.3 km await tester.dragUntilVisible(find.text('9.5'), scrollableFinder, const Offset(0, -50)); final segment3CABStart = findDASTableRowByText('8.3'); - final segment3CABStartIcon = find.descendant(of: segment3CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); + final segment3CABStartIcon = + find.descendant(of: segment3CABStart, matching: find.byKey(CABSignalingRow.cabSignalingStartIconKey)); expect(segment3CABStartIcon, findsOneWidget); }); + + testWidgets('test if track equipment is displayed correctly', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: 'T1'); + + final scrollableFinder = find.byType(ListView); + expect(scrollableFinder, findsOneWidget); + + // check ExtendedSpeedReversingPossible from Genève-Aéroport to Gland + _checkTrackEquipmentOnServicePoint('Genève-Aéroport', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + _checkTrackEquipmentOnServicePoint('Genève', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + _checkTrackEquipmentOnServicePoint('Gland', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + final segment1CABStop = findDASTableRowByText('33.8').last; + final segment1CABStopTrackEquipment = find.descendant( + of: segment1CABStop, matching: find.byKey(TrackEquipmentCellBody.extendedSpeedReversingPossibleKey)); + expect(segment1CABStopTrackEquipment, findsOneWidget); + + // check ConventionalSpeedReversingImpossible from Morges to Onnens-Bonvillars + await tester.dragUntilVisible(find.text('Onnens-Bonvillars'), scrollableFinder, const Offset(0, -50)); + final segment2CABStart = findDASTableRowByText('12.5').first; + final segment2CABStartTrackEquipment = find.descendant( + of: segment2CABStart, matching: find.byKey(TrackEquipmentCellBody.conventionalSpeedReversingImpossible)); + expect(segment2CABStartTrackEquipment, findsOneWidget); + _checkTrackEquipmentOnServicePoint('Morges', TrackEquipmentCellBody.conventionalSpeedReversingImpossible); + _checkTrackEquipmentOnServicePoint( + 'Yverdon-les-Bains', TrackEquipmentCellBody.conventionalSpeedReversingImpossible); + _checkTrackEquipmentOnServicePoint( + 'Onnens-Bonvillars', TrackEquipmentCellBody.conventionalSpeedReversingImpossible); + + // check ExtendedSpeedReversingPossibleKey from Neuchâtel to Rothrist + await tester.dragUntilVisible(find.text('Grenchen Süd'), scrollableFinder, const Offset(0, -50)); + _checkTrackEquipmentOnServicePoint('Neuchâtel', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey, + hasConvExtSpeedBorder: true); + _checkTrackEquipmentOnServicePoint('Biel/Bienne', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + _checkTrackEquipmentOnServicePoint('Lengnau', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + _checkTrackEquipmentOnServicePoint('Grenchen Süd', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + await tester.dragUntilVisible(find.text('Rothrist'), scrollableFinder, const Offset(0, -50)); + _checkTrackEquipmentOnServicePoint('Solothurn', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + _checkTrackEquipmentOnServicePoint('WANZ', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + _checkTrackEquipmentOnServicePoint('Rothrist', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + + // check ExtendedSpeedReversingPossibleKey in Olten + await tester.dragUntilVisible(find.text('Aarau'), scrollableFinder, const Offset(0, -50)); + _checkTrackEquipmentOnServicePoint('Olten', TrackEquipmentCellBody.conventionalSpeedReversingImpossible, + hasConvExtSpeedBorder: true); + final segment2CABEnd = findDASTableRowByText('39.9').first; + final segment2CABEndTrackEquipment = find.descendant( + of: segment2CABEnd, matching: find.byKey(TrackEquipmentCellBody.conventionalSpeedReversingImpossible)); + expect(segment2CABEndTrackEquipment, findsOneWidget); + + // check ExtendedSpeedReversingImpossibleKey from Zürich HB to Opfikon Süd + await tester.dragUntilVisible(find.text('Flughafen'), scrollableFinder, const Offset(0, -50)); + final segment3CABStart = findDASTableRowByText('8.3').first; + final segment3CABStartTrackEquipment = find.descendant( + of: segment3CABStart, matching: find.byKey(TrackEquipmentCellBody.extendedSpeedReversingImpossibleKey)); + expect(segment3CABStartTrackEquipment, findsOneWidget); + _checkTrackEquipmentOnServicePoint('Zürich HB', TrackEquipmentCellBody.extendedSpeedReversingImpossibleKey); + _checkTrackEquipmentOnServicePoint('Opfikon Süd', TrackEquipmentCellBody.extendedSpeedReversingImpossibleKey); + + // check ExtendedSpeedReversingImpossibleKey in Flughafen + _checkTrackEquipmentOnServicePoint('Flughafen', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey); + }); }); } +void _checkTrackEquipmentOnServicePoint(String name, Key expectedKey, {bool hasConvExtSpeedBorder = false}) { + final servicePointRow = findDASTableRowByText(name); + final trackEquipment = find.descendant(of: servicePointRow, matching: find.byKey(expectedKey)); + expect(trackEquipment, findsOneWidget); + + final convExtSpeedBorder = find.descendant( + of: servicePointRow, matching: find.byKey(TrackEquipmentCellBody.conventionalExtendedSpeedBorderKey)); + expect(convExtSpeedBorder, hasConvExtSpeedBorder ? findsOneWidget : findsNothing); +} + /// 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. From 351dd8e31339eb6cb2d45538e7a3f6652102fcba Mon Sep 17 00:00:00 2001 From: u221711 Date: Wed, 18 Dec 2024 10:15:04 +0100 Subject: [PATCH 13/22] fix rendering issue --- .../table/cells/track_equipment_cell_body.dart | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart index 384e4e76..ce3d3f1a 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart @@ -165,21 +165,11 @@ class _CumulativeDashedLinePainter extends CustomPainter { final dashPatternLength = dashHeights.reduce((a, b) => a + b) + (dashSpace * (dashHeights.length)); final offsetInPattern = cumulativeHeight % dashPatternLength; - // Determine the starting dash index based on the offset - int dashIndex = 0; - double accumulatedLength = 0; - for (int i = 0; i < dashHeights.length; i++) { - accumulatedLength += dashHeights[i] + dashSpace; - if (accumulatedLength > offsetInPattern) { - dashIndex = i + 1; - break; - } - } - final paint = Paint() ..color = Colors.black ..strokeWidth = width; + int dashIndex = 0; double startY = -offsetInPattern; while (startY < size.height) { dashIndex = dashIndex % dashHeights.length; From aef8aa8325db0acd16b829c8655db5311eb57118 Mon Sep 17 00:00:00 2001 From: u221711 Date: Wed, 18 Dec 2024 10:53:50 +0100 Subject: [PATCH 14/22] quick fix for not throwing exception on event message --- .../sfera/src/service/sfera_service_impl.dart | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) 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 a8821244..48b1fc37 100644 --- a/das_client/lib/sfera/src/service/sfera_service_impl.dart +++ b/das_client/lib/sfera/src/service/sfera_service_impl.dart @@ -10,6 +10,7 @@ import 'package:das_client/sfera/src/model/enums/das_driving_mode.dart'; import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; import 'package:das_client/sfera/src/model/train_characteristics.dart'; import 'package:das_client/sfera/src/service/handler/journey_profile_reply_handler.dart'; import 'package:das_client/sfera/src/service/handler/segment_profile_reply_handler.dart'; @@ -59,20 +60,22 @@ class SferaServiceImpl implements SferaService { } void _init() { - _mqttStreamSubscription = _mqttService.messageStream.listen((message) async { - final sferaG2bReplyMessage = SferaReplyParser.parse(message); - if (!sferaG2bReplyMessage.validate()) { - Fimber.w('Validation failed for MQTT response $message'); - return; - } + _mqttStreamSubscription = _mqttService.messageStream.listen((xmlMessage) async { + final message = SferaReplyParser.parse(xmlMessage); + if (message is SferaG2bReplyMessage) { + if (!message.validate()) { + Fimber.w('Validation failed for MQTT response $xmlMessage'); + return; + } - var handled = false; - for (final handler in List.from(_messageHandlers)) { - handled |= await handler.handleMessage(sferaG2bReplyMessage); - } + var handled = false; + for (final handler in List.from(_messageHandlers)) { + handled |= await handler.handleMessage(message); + } - if (!handled) { - Fimber.w('Could not handle sfera message $message'); + if (!handled) { + Fimber.w('Could not handle sfera reply message $xmlMessage'); + } } }); } From dbf176220bbd40ad2ec82ebdb9d8cf8cade5d608 Mon Sep 17 00:00:00 2001 From: u221711 Date: Wed, 18 Dec 2024 11:24:20 +0100 Subject: [PATCH 15/22] fix something I broke :) --- .../train_journey/widgets/train_journey.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 29e7ed63..e255261f 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 @@ -119,12 +119,15 @@ class TrainJourney extends StatelessWidget { trackEquipmentRenderData: renderData) .build(context); case Datatype.cabSignaling: + final data = rowData as CABSignaling; return CABSignalingRow( - metadata: journey.metadata, - data: rowData as CABSignaling, - settings: settings, - trackEquipmentRenderData: renderData) - .build(context); + metadata: journey.metadata, + data: data, + settings: settings, + trackEquipmentRenderData: renderData.copyWith( + isCABStart: data.isStart, + isCABEnd: data.isEnd, + )).build(context); } }); } From fc76192c4ce25c6450c16c335cc0113096b6fb27 Mon Sep 17 00:00:00 2001 From: u221711 Date: Thu, 19 Dec 2024 14:29:04 +0100 Subject: [PATCH 16/22] feat: implement basic sfera event handling --- das_client/ios/Podfile.lock | 2 +- das_client/l10n/strings_de.arb | 2 +- .../sfera/src/mapper/sfera_model_mapper.dart | 12 +- das_client/lib/sfera/src/model/delay.dart | 14 ++ .../sfera/src/model/g2b_event_payload.dart | 14 ++ .../sfera/src/model/g2b_reply_payload.dart | 3 + das_client/lib/sfera/src/model/own_train.dart | 20 +++ .../src/model/related_train_information.dart | 16 +++ .../src/model/sfera_g2b_event_message.dart | 18 +++ .../src/model/train_location_information.dart | 16 +++ .../event/journey_profile_event_handler.dart | 30 ++++ ...lated_train_information_event_handler.dart | 22 +++ .../event/sfera_event_message_handler.dart | 11 ++ .../journey_profile_reply_handler.dart | 23 ---- .../segment_profile_reply_handler.dart | 23 ---- .../handler/sfera_message_handler.dart | 5 - .../sfera/src/service/sfera_service_impl.dart | 128 ++++++++++++------ .../task/request_journey_profile_task.dart | 15 +- .../task/request_segment_profiles_task.dart | 2 +- .../sfera/src/service/task/sfera_task.dart | 6 +- .../lib/sfera/src/sfera_reply_parser.dart | 18 +++ das_client/lib/util/error_code.dart | 6 +- .../test/sfera/mapper/sfera_mapper_test.dart | 55 ++++++-- ...era_request_segment_profile_task_test.dart | 2 +- 24 files changed, 339 insertions(+), 124 deletions(-) create mode 100644 das_client/lib/sfera/src/model/delay.dart create mode 100644 das_client/lib/sfera/src/model/g2b_event_payload.dart create mode 100644 das_client/lib/sfera/src/model/own_train.dart create mode 100644 das_client/lib/sfera/src/model/related_train_information.dart create mode 100644 das_client/lib/sfera/src/model/sfera_g2b_event_message.dart create mode 100644 das_client/lib/sfera/src/model/train_location_information.dart create mode 100644 das_client/lib/sfera/src/service/event/journey_profile_event_handler.dart create mode 100644 das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart create mode 100644 das_client/lib/sfera/src/service/event/sfera_event_message_handler.dart delete mode 100644 das_client/lib/sfera/src/service/handler/journey_profile_reply_handler.dart delete mode 100644 das_client/lib/sfera/src/service/handler/segment_profile_reply_handler.dart delete mode 100644 das_client/lib/sfera/src/service/handler/sfera_message_handler.dart diff --git a/das_client/ios/Podfile.lock b/das_client/ios/Podfile.lock index 2e39e64c..0ef49177 100644 --- a/das_client/ios/Podfile.lock +++ b/das_client/ios/Podfile.lock @@ -68,4 +68,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: d9dad56c0cd0b4fd8b4fe3034a53fd42a0b990f6 -COCOAPODS: 1.16.1 +COCOAPODS: 1.15.2 diff --git a/das_client/l10n/strings_de.arb b/das_client/l10n/strings_de.arb index d4f8ae78..0493bf2b 100644 --- a/das_client/l10n/strings_de.arb +++ b/das_client/l10n/strings_de.arb @@ -43,7 +43,7 @@ "c_error_sfera_handshake_rejected": "Server hat die Verbindung abgelehnt", "c_error_sfera_request_timeout": "Timeout bei der Anfrage", "c_error_sfera_jp_unavailable": "Fahrordnung nicht vorhanden", - "c_error_sfera_sp_invalid": "Unvollständige Daten erhalten", + "c_error_sfera_invalid": "Unvollständige Daten erhalten", "c_connection_track_weiche": "Weiche", "c_button_confirm": "Übernehmen" } \ No newline at end of file diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart index d65e1941..a4b83c75 100644 --- a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -27,6 +27,7 @@ import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/multilingual_text.dart'; import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; +import 'package:das_client/sfera/src/model/related_train_information.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; import 'package:das_client/sfera/src/model/speeds.dart'; import 'package:das_client/sfera/src/model/taf_tap_location.dart'; @@ -42,10 +43,13 @@ class SferaModelMapper { static const String _protectionSectionNspFacultativeName = 'facultative'; static const String _protectionSectionNspLengthTypeName = 'lengthType'; - static Journey mapToJourney(JourneyProfile journeyProfile, List segmentProfiles, - List trainCharacteristics) { + static Journey mapToJourney( + {required JourneyProfile journeyProfile, + List segmentProfiles = const [], + List trainCharacteristics = const [], + RelatedTrainInformation? relatedTrainInformation}) { try { - return _mapToJourney(journeyProfile, segmentProfiles, trainCharacteristics); + return _mapToJourney(journeyProfile, segmentProfiles, trainCharacteristics, relatedTrainInformation); } catch (e, s) { Fimber.e('Error mapping journey-/segment profiles to journey:', ex: e, stacktrace: s); return Journey.invalid(); @@ -53,7 +57,7 @@ class SferaModelMapper { } static Journey _mapToJourney(JourneyProfile journeyProfile, List segmentProfiles, - List trainCharacteristics) { + List trainCharacteristics, RelatedTrainInformation? relatedTrainInformation) { final journeyData = []; final segmentProfilesLists = journeyProfile.segmentProfilesLists.toList(); diff --git a/das_client/lib/sfera/src/model/delay.dart b/das_client/lib/sfera/src/model/delay.dart new file mode 100644 index 00000000..308ffff6 --- /dev/null +++ b/das_client/lib/sfera/src/model/delay.dart @@ -0,0 +1,14 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class Delay extends SferaXmlElement { + static const String elementType = 'Delay'; + + Delay({super.type = elementType, super.attributes, super.children, super.value}); + + String get delay => attributes['Delay']!; + + @override + bool validate() { + return validateHasAttribute('Delay') && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/g2b_event_payload.dart b/das_client/lib/sfera/src/model/g2b_event_payload.dart new file mode 100644 index 00000000..97e1f4fd --- /dev/null +++ b/das_client/lib/sfera/src/model/g2b_event_payload.dart @@ -0,0 +1,14 @@ +import 'package:das_client/sfera/src/model/journey_profile.dart'; +import 'package:das_client/sfera/src/model/related_train_information.dart'; +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class G2bEventPayload extends SferaXmlElement { + static const String elementType = 'G2B_EventPayload'; + + G2bEventPayload({super.type = elementType, super.attributes, super.children, super.value}); + + RelatedTrainInformation? get relatedTrainInformation => children.whereType().firstOrNull; + + Iterable get journeyProfiles => children.whereType(); + +} diff --git a/das_client/lib/sfera/src/model/g2b_reply_payload.dart b/das_client/lib/sfera/src/model/g2b_reply_payload.dart index 584d8095..7397d31e 100644 --- a/das_client/lib/sfera/src/model/g2b_reply_payload.dart +++ b/das_client/lib/sfera/src/model/g2b_reply_payload.dart @@ -1,4 +1,5 @@ import 'package:das_client/sfera/src/model/journey_profile.dart'; +import 'package:das_client/sfera/src/model/related_train_information.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; import 'package:das_client/sfera/src/model/train_characteristics.dart'; @@ -13,4 +14,6 @@ class G2bReplyPayload extends SferaXmlElement { Iterable get segmentProfiles => children.whereType(); Iterable get trainCharacteristics => children.whereType(); + + Iterable get relatedTrainInformation => children.whereType(); } diff --git a/das_client/lib/sfera/src/model/own_train.dart b/das_client/lib/sfera/src/model/own_train.dart new file mode 100644 index 00000000..e24cc276 --- /dev/null +++ b/das_client/lib/sfera/src/model/own_train.dart @@ -0,0 +1,20 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/train_identification.dart'; +import 'package:das_client/sfera/src/model/train_location_information.dart'; + +class OwnTrain extends SferaXmlElement { + static const String elementType = 'OwnTrain'; + + OwnTrain({super.type = elementType, super.attributes, super.children, super.value}); + + TrainIdentification get trainIdentification => children.whereType().first; + + TrainLocationInformation get trainLocationInformation => children.whereType().first; + + @override + bool validate() { + return validateHasChildOfType() && + validateHasChildOfType() && + super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/related_train_information.dart b/das_client/lib/sfera/src/model/related_train_information.dart new file mode 100644 index 00000000..dea87d3f --- /dev/null +++ b/das_client/lib/sfera/src/model/related_train_information.dart @@ -0,0 +1,16 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +import 'package:das_client/sfera/src/model/own_train.dart'; + +class RelatedTrainInformation extends SferaXmlElement { + static const String elementType = 'RelatedTrainInformation'; + + RelatedTrainInformation({super.type = elementType, super.attributes, super.children, super.value}); + + OwnTrain get ownTrain => children.whereType().first; + + @override + bool validate() { + return validateHasChildOfType() && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/sfera_g2b_event_message.dart b/das_client/lib/sfera/src/model/sfera_g2b_event_message.dart new file mode 100644 index 00000000..19831779 --- /dev/null +++ b/das_client/lib/sfera/src/model/sfera_g2b_event_message.dart @@ -0,0 +1,18 @@ +import 'package:das_client/sfera/src/model/g2b_event_payload.dart'; +import 'package:das_client/sfera/src/model/message_header.dart'; +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class SferaG2bEventMessage extends SferaXmlElement { + static const String elementType = 'SFERA_G2B_EventMessage'; + + SferaG2bEventMessage({super.type = elementType, super.attributes, super.children, super.value}); + + MessageHeader get messageHeader => children.whereType().first; + + G2bEventPayload? get payload => children.whereType().firstOrNull; + + @override + bool validate() { + return validateHasChildOfType() && validateHasChildOfType() && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/train_location_information.dart b/das_client/lib/sfera/src/model/train_location_information.dart new file mode 100644 index 00000000..9568c69e --- /dev/null +++ b/das_client/lib/sfera/src/model/train_location_information.dart @@ -0,0 +1,16 @@ +import 'package:das_client/sfera/src/model/delay.dart'; +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class TrainLocationInformation extends SferaXmlElement { + static const String elementType = 'TrainLocationInformation'; + + + TrainLocationInformation({super.type = elementType, super.attributes, super.children, super.value}); + + Delay get delay => children.whereType().first; + + @override + bool validate() { + return validateHasChildOfType() && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/service/event/journey_profile_event_handler.dart b/das_client/lib/sfera/src/service/event/journey_profile_event_handler.dart new file mode 100644 index 00000000..e0ad86e5 --- /dev/null +++ b/das_client/lib/sfera/src/service/event/journey_profile_event_handler.dart @@ -0,0 +1,30 @@ +import 'package:das_client/sfera/src/model/journey_profile.dart'; +import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart'; +import 'package:das_client/sfera/src/repo/sfera_repository.dart'; +import 'package:das_client/sfera/src/service/event/sfera_event_message_handler.dart'; +import 'package:fimber/fimber.dart'; + +class JourneyProfileEventHandler extends SferaEventMessageHandler { + final SferaRepository _sferaRepository; + + JourneyProfileEventHandler(super.onMessageHandled, this._sferaRepository); + + @override + Future handleMessage(SferaG2bEventMessage eventMessage) async { + if (eventMessage.payload == null || eventMessage.payload!.journeyProfiles.isEmpty) { + return false; + } + + Fimber.i('Updating journey profiles...'); + for (final journeyProfile in eventMessage.payload!.journeyProfiles) { + await _sferaRepository.saveJourneyProfile(journeyProfile); + } + + if (eventMessage.payload!.journeyProfiles.length > 1) { + Fimber.w('Received more then 1 journey profile which is not supported, using first one provided'); + } + + onMessageHandled(this, eventMessage.payload!.journeyProfiles.first); + return true; + } +} diff --git a/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart b/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart new file mode 100644 index 00000000..5e34db2d --- /dev/null +++ b/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart @@ -0,0 +1,22 @@ +import 'package:das_client/sfera/src/model/journey_profile.dart'; +import 'package:das_client/sfera/src/model/related_train_information.dart'; +import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart'; +import 'package:das_client/sfera/src/repo/sfera_repository.dart'; +import 'package:das_client/sfera/src/service/event/sfera_event_message_handler.dart'; +import 'package:fimber/fimber.dart'; + +class RelatedTrainInformationEventHandler extends SferaEventMessageHandler { + + RelatedTrainInformationEventHandler(super.onMessageHandled); + + @override + Future handleMessage(SferaG2bEventMessage eventMessage) async { + if (eventMessage.payload == null || eventMessage.payload!.relatedTrainInformation == null) { + return false; + } + + Fimber.i('Received new related train information...'); + onMessageHandled(this, eventMessage.payload!.relatedTrainInformation!); + return true; + } +} diff --git a/das_client/lib/sfera/src/service/event/sfera_event_message_handler.dart b/das_client/lib/sfera/src/service/event/sfera_event_message_handler.dart new file mode 100644 index 00000000..82682dcb --- /dev/null +++ b/das_client/lib/sfera/src/service/event/sfera_event_message_handler.dart @@ -0,0 +1,11 @@ +import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart'; + +typedef MessageHandled = void Function(SferaEventMessageHandler handler, T data); + +abstract class SferaEventMessageHandler { + SferaEventMessageHandler(this.onMessageHandled); + + final MessageHandled onMessageHandled; + + Future handleMessage(SferaG2bEventMessage eventMessage); +} 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 deleted file mode 100644 index 57dbf420..00000000 --- a/das_client/lib/sfera/src/service/handler/journey_profile_reply_handler.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; -import 'package:das_client/sfera/src/repo/sfera_repository.dart'; -import 'package:das_client/sfera/src/service/handler/sfera_message_handler.dart'; -import 'package:fimber/fimber.dart'; - -class JourneyProfileReplyHandler implements SferaMessageHandler { - final SferaRepository _sferaRepository; - JourneyProfileReplyHandler(this._sferaRepository); - - @override - Future handleMessage(SferaG2bReplyMessage message) async { - if (message.payload == null || message.payload!.journeyProfiles.isEmpty) { - return false; - } - - Fimber.i('Updating journey profiles...'); - for (final journeyProfile in message.payload!.journeyProfiles) { - await _sferaRepository.saveJourneyProfile(journeyProfile); - } - - return true; - } -} 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 deleted file mode 100644 index 0266502f..00000000 --- a/das_client/lib/sfera/src/service/handler/segment_profile_reply_handler.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; -import 'package:das_client/sfera/src/repo/sfera_repository.dart'; -import 'package:das_client/sfera/src/service/handler/sfera_message_handler.dart'; -import 'package:fimber/fimber.dart'; - -class SegmentProfileReplyHandler implements SferaMessageHandler { - final SferaRepository _sferaRepository; - SegmentProfileReplyHandler(this._sferaRepository); - - @override - Future handleMessage(SferaG2bReplyMessage message) async { - if (message.payload == null || message.payload!.segmentProfiles.isEmpty) { - return false; - } - - Fimber.i('Updating segment profiles...'); - for (final segmentProfile in message.payload!.segmentProfiles) { - await _sferaRepository.saveSegmentProfile(segmentProfile); - } - - return true; - } -} diff --git a/das_client/lib/sfera/src/service/handler/sfera_message_handler.dart b/das_client/lib/sfera/src/service/handler/sfera_message_handler.dart deleted file mode 100644 index afc26a56..00000000 --- a/das_client/lib/sfera/src/service/handler/sfera_message_handler.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; - -abstract class SferaMessageHandler { - Future handleMessage(SferaG2bReplyMessage replyMessage); -} 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 48b1fc37..d3115ddd 100644 --- a/das_client/lib/sfera/src/service/sfera_service_impl.dart +++ b/das_client/lib/sfera/src/service/sfera_service_impl.dart @@ -8,13 +8,15 @@ import 'package:das_client/sfera/sfera_component.dart'; import 'package:das_client/sfera/src/mapper/sfera_model_mapper.dart'; import 'package:das_client/sfera/src/model/enums/das_driving_mode.dart'; import 'package:das_client/sfera/src/model/journey_profile.dart'; +import 'package:das_client/sfera/src/model/related_train_information.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; +import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart'; import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; import 'package:das_client/sfera/src/model/train_characteristics.dart'; -import 'package:das_client/sfera/src/service/handler/journey_profile_reply_handler.dart'; -import 'package:das_client/sfera/src/service/handler/segment_profile_reply_handler.dart'; -import 'package:das_client/sfera/src/service/handler/sfera_message_handler.dart'; +import 'package:das_client/sfera/src/service/event/journey_profile_event_handler.dart'; +import 'package:das_client/sfera/src/service/event/related_train_information_event_handler.dart'; +import 'package:das_client/sfera/src/service/event/sfera_event_message_handler.dart'; import 'package:das_client/sfera/src/service/task/handshake_task.dart'; import 'package:das_client/sfera/src/service/task/request_journey_profile_task.dart'; import 'package:das_client/sfera/src/service/task/request_segment_profiles_task.dart'; @@ -30,7 +32,8 @@ class SferaServiceImpl implements SferaService { final Authenticator _authenticator; StreamSubscription? _mqttStreamSubscription; - final List _messageHandlers = []; + final List _tasks = []; + final List _eventMessageHandler = []; final _stateSubject = BehaviorSubject.seeded(SferaServiceState.disconnected); @@ -45,6 +48,7 @@ class SferaServiceImpl implements SferaService { JourneyProfile? _journeyProfile; final List _segmentProfiles = []; final List _trainCharacteristics = []; + RelatedTrainInformation? _relatedTrainInformation; @override ErrorCode? lastErrorCode; @@ -60,21 +64,31 @@ class SferaServiceImpl implements SferaService { } void _init() { + _addEventMessageHandlers(); _mqttStreamSubscription = _mqttService.messageStream.listen((xmlMessage) async { final message = SferaReplyParser.parse(xmlMessage); + if (!message.validate()) { + Fimber.w('Validation failed for MQTT response $xmlMessage'); + return; + } + if (message is SferaG2bReplyMessage) { - if (!message.validate()) { - Fimber.w('Validation failed for MQTT response $xmlMessage'); - return; + var handled = false; + for (final handler in List.from(_tasks)) { + handled |= await handler.handleMessage(message); } + if (!handled) { + Fimber.w('Could not handle sfera reply message $xmlMessage'); + } + } else if (message is SferaG2bEventMessage) { var handled = false; - for (final handler in List.from(_messageHandlers)) { + for (final handler in List.from(_eventMessageHandler)) { handled |= await handler.handleMessage(message); } if (!handled) { - Fimber.w('Could not handle sfera reply message $xmlMessage'); + Fimber.w('Could not handle sfera event message $xmlMessage'); } } }); @@ -84,7 +98,7 @@ class SferaServiceImpl implements SferaService { Future connect(OtnId otnId) async { Fimber.i('Starting new connection for $otnId'); _otnId = otnId; - _messageHandlers.clear(); + _tasks.clear(); lastErrorCode = null; _stateSubject.add(SferaServiceState.connecting); @@ -95,7 +109,7 @@ class SferaServiceImpl implements SferaService { final drivingMode = user.roles.contains(Role.driver) ? DasDrivingMode.dasNotConnected : DasDrivingMode.readOnly; final handshakeTask = HandshakeTask(mqttService: _mqttService, otnId: otnId, dasDrivingMode: drivingMode); - _messageHandlers.add(handshakeTask); + _tasks.add(handshakeTask); handshakeTask.execute(onTaskCompleted, onTaskFailed); } else { _otnId = null; @@ -105,43 +119,62 @@ class SferaServiceImpl implements SferaService { } void onTaskCompleted(SferaTask task, dynamic data) async { - _messageHandlers.remove(task); + _tasks.remove(task); Fimber.i('Task $task completed'); if (task is HandshakeTask) { _stateSubject.add(SferaServiceState.loadingJourney); final requestJourneyTask = RequestJourneyProfileTask(mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: _otnId!); - _messageHandlers.add(requestJourneyTask); + _tasks.add(requestJourneyTask); requestJourneyTask.execute(onTaskCompleted, onTaskFailed); } else if (task is RequestJourneyProfileTask) { _stateSubject.add(SferaServiceState.loadingAdditionalData); - final requestSegmentProfilesTask = RequestSegmentProfilesTask( - mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: _otnId!, journeyProfile: data); - final requestTrainCharacteristicsTask = RequestTrainCharacteristicsTask( - mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: _otnId!, journeyProfile: data); - _journeyProfile = data; - _messageHandlers.add(requestSegmentProfilesTask); - _messageHandlers.add(requestTrainCharacteristicsTask); - requestSegmentProfilesTask.execute(onTaskCompleted, onTaskFailed); - requestTrainCharacteristicsTask.execute(onTaskCompleted, onTaskFailed); + final dataList = data as List; + _journeyProfile = dataList.whereType().first; + _relatedTrainInformation = dataList.whereType().firstOrNull; + _startSegmentProfileAndTCTask(); } - if (_stateSubject.value == SferaServiceState.loadingAdditionalData && _allTaskedCompleted()) { - _addMessageHandlers(); - await _refreshSegmentProfiles(); - await _refreshTrainCharacteristics(); - final success = _updateJourney(); - if (success) { - _stateSubject.add(SferaServiceState.connected); - } else { - lastErrorCode = ErrorCode.sferaSpInvalid; - disconnect(); + if (_allTasksCompleted()) { + switch (_stateSubject.value) { + case SferaServiceState.loadingAdditionalData: + await _refreshSegmentProfiles(); + await _refreshTrainCharacteristics(); + final success = _updateJourney(); + if (success) { + _stateSubject.add(SferaServiceState.connected); + } else { + disconnect(); + } + break; + case SferaServiceState.connected: + await _refreshSegmentProfiles(); + await _refreshTrainCharacteristics(); + _updateJourney(); + default: } } } - bool _allTaskedCompleted() { - return _messageHandlers.whereType().isEmpty; + void _startSegmentProfileAndTCTask() { + final requestSegmentProfilesTask = RequestSegmentProfilesTask( + mqttService: _mqttService, + sferaRepository: _sferaRepository, + otnId: _otnId!, + journeyProfile: _journeyProfile!); + final requestTrainCharacteristicsTask = RequestTrainCharacteristicsTask( + mqttService: _mqttService, + sferaRepository: _sferaRepository, + otnId: _otnId!, + journeyProfile: _journeyProfile!); + _tasks.add(requestSegmentProfilesTask); + _tasks.add(requestTrainCharacteristicsTask); + requestSegmentProfilesTask.execute(onTaskCompleted, onTaskFailed); + requestTrainCharacteristicsTask.execute(onTaskCompleted, onTaskFailed); + } + + bool _allTasksCompleted() { + return _tasks.whereType().isEmpty; } Future _refreshSegmentProfiles() async { @@ -167,7 +200,7 @@ class SferaServiceImpl implements SferaService { for (final element in _journeyProfile!.trainCharactericsRefSet) { final trainCharactericsEntity = - await _sferaRepository.findTrainCharacteristics(element.tcId, element.versionMajor, element.versionMinor); + await _sferaRepository.findTrainCharacteristics(element.tcId, element.versionMajor, element.versionMinor); final trainCharacterics = trainCharactericsEntity?.toDomain(); if (trainCharacterics != null && trainCharacterics.validate()) { _trainCharacteristics.add(trainCharacterics); @@ -181,25 +214,40 @@ class SferaServiceImpl implements SferaService { bool _updateJourney() { if (_journeyProfile != null && _segmentProfiles.isNotEmpty) { Fimber.i('Updating journey stream...'); - final newJourney = SferaModelMapper.mapToJourney(_journeyProfile!, _segmentProfiles, _trainCharacteristics); + final newJourney = SferaModelMapper.mapToJourney( + journeyProfile: _journeyProfile!, + segmentProfiles: _segmentProfiles, + trainCharacteristics: _trainCharacteristics, + relatedTrainInformation: _relatedTrainInformation); if (newJourney.valid) { _journeyProfileSubject.add(newJourney); Fimber.i('Journey updates successfully.'); return true; } else { Fimber.w('Failed to update journey as it is not valid'); + lastErrorCode = ErrorCode.sferaInvalid; } } return false; } - void _addMessageHandlers() { - _messageHandlers.add(JourneyProfileReplyHandler(_sferaRepository)); - _messageHandlers.add(SegmentProfileReplyHandler(_sferaRepository)); + void _addEventMessageHandlers() { + _eventMessageHandler.add(JourneyProfileEventHandler(onJourneyProfileUpdated, _sferaRepository)); + _eventMessageHandler.add(RelatedTrainInformationEventHandler(onRelatedTrainInformationUpdated)); + } + + void onJourneyProfileUpdated(SferaEventMessageHandler handler, JourneyProfile data) async { + _journeyProfile = data; + _startSegmentProfileAndTCTask(); + } + + void onRelatedTrainInformationUpdated(SferaEventMessageHandler handler, RelatedTrainInformation data) async { + _relatedTrainInformation = data; + _updateJourney(); } void onTaskFailed(SferaTask task, ErrorCode errorCode) { - _messageHandlers.remove(task); + _tasks.remove(task); lastErrorCode = errorCode; Fimber.e('Task $task failed with error code $errorCode'); if (_stateSubject.value != SferaServiceState.connected) { 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 a955d351..4067ab84 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 @@ -1,7 +1,6 @@ import 'package:das_client/mqtt/mqtt_component.dart'; import 'package:das_client/sfera/src/model/b2g_request.dart'; import 'package:das_client/sfera/src/model/enums/jp_status.dart'; -import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/jp_request.dart'; import 'package:das_client/sfera/src/model/otn_id.dart'; import 'package:das_client/sfera/src/model/sfera_b2g_request_message.dart'; @@ -13,7 +12,7 @@ import 'package:das_client/sfera/src/service/task/sfera_task.dart'; import 'package:das_client/util/error_code.dart'; import 'package:fimber/fimber.dart'; -class RequestJourneyProfileTask extends SferaTask { +class RequestJourneyProfileTask extends SferaTask> { RequestJourneyProfileTask( {required MqttService mqttService, required SferaRepository sferaRepository, required this.otnId, super.timeout}) : _mqttService = mqttService, @@ -23,11 +22,11 @@ class RequestJourneyProfileTask extends SferaTask { final OtnId otnId; final SferaRepository _sferaRepository; - late TaskCompleted _taskCompletedCallback; + late TaskCompleted> _taskCompletedCallback; late TaskFailed _taskFailedCallback; @override - Future execute(TaskCompleted onCompleted, TaskFailed onFailed) async { + Future execute(TaskCompleted> onCompleted, TaskFailed onFailed) async { _taskCompletedCallback = onCompleted; _taskFailedCallback = onFailed; @@ -78,7 +77,13 @@ class RequestJourneyProfileTask extends SferaTask { await _sferaRepository.saveJourneyProfile(journeyProfile); } - _taskCompletedCallback(this, replyMessage.payload!.journeyProfiles.first); + final result = []; + result.addAll(replyMessage.payload!.journeyProfiles); + result.addAll(replyMessage.payload!.segmentProfiles); + result.addAll(replyMessage.payload!.trainCharacteristics); + result.addAll(replyMessage.payload!.relatedTrainInformation); + + _taskCompletedCallback(this, result); return true; } return false; 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 26007cd5..a3511b44 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 @@ -101,7 +101,7 @@ class RequestSegmentProfilesTask extends SferaTask> { if (allValid) { _taskCompletedCallback(this, replyMessage.payload!.segmentProfiles.toList()); } else { - _taskFailedCallback(this, ErrorCode.sferaSpInvalid); + _taskFailedCallback(this, ErrorCode.sferaInvalid); } return true; diff --git a/das_client/lib/sfera/src/service/task/sfera_task.dart b/das_client/lib/sfera/src/service/task/sfera_task.dart index ede1d366..fd89eb48 100644 --- a/das_client/lib/sfera/src/service/task/sfera_task.dart +++ b/das_client/lib/sfera/src/service/task/sfera_task.dart @@ -1,13 +1,13 @@ import 'dart:async'; -import 'package:das_client/sfera/src/service/handler/sfera_message_handler.dart'; +import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; import 'package:das_client/util/error_code.dart'; import 'package:fimber/fimber.dart'; typedef TaskFailed = void Function(SferaTask task, ErrorCode errorCode); typedef TaskCompleted = void Function(SferaTask task, T? data); -abstract class SferaTask implements SferaMessageHandler { +abstract class SferaTask { SferaTask({Duration? timeout}) : _timeout = timeout ?? const Duration(seconds: 15); final Duration _timeout; @@ -15,6 +15,8 @@ abstract class SferaTask implements SferaMessageHandler { Future execute(TaskCompleted onCompleted, TaskFailed onFailed); + Future handleMessage(SferaG2bReplyMessage replyMessage); + void startTimeout( TaskFailed onFailed, ) { diff --git a/das_client/lib/sfera/src/sfera_reply_parser.dart b/das_client/lib/sfera/src/sfera_reply_parser.dart index 2a5dfdd1..f1bec1a3 100644 --- a/das_client/lib/sfera/src/sfera_reply_parser.dart +++ b/das_client/lib/sfera/src/sfera_reply_parser.dart @@ -5,6 +5,8 @@ import 'package:das_client/sfera/src/model/current_limitation_change.dart'; import 'package:das_client/sfera/src/model/current_limitation_start.dart'; import 'package:das_client/sfera/src/model/curve_speed.dart'; import 'package:das_client/sfera/src/model/das_operating_modes_selected.dart'; +import 'package:das_client/sfera/src/model/delay.dart'; +import 'package:das_client/sfera/src/model/g2b_event_payload.dart'; import 'package:das_client/sfera/src/model/g2b_reply_payload.dart'; import 'package:das_client/sfera/src/model/handshake_acknowledgement.dart'; import 'package:das_client/sfera/src/model/handshake_reject.dart'; @@ -19,8 +21,11 @@ import 'package:das_client/sfera/src/model/network_specific_area.dart'; import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; import 'package:das_client/sfera/src/model/network_specific_point.dart'; import 'package:das_client/sfera/src/model/otn_id.dart'; +import 'package:das_client/sfera/src/model/own_train.dart'; +import 'package:das_client/sfera/src/model/related_train_information.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; import 'package:das_client/sfera/src/model/segment_profile_list.dart'; +import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart'; import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; import 'package:das_client/sfera/src/model/signal.dart'; @@ -52,6 +57,7 @@ import 'package:das_client/sfera/src/model/tp_name.dart'; import 'package:das_client/sfera/src/model/train_characteristics.dart'; import 'package:das_client/sfera/src/model/train_characteristics_ref.dart'; import 'package:das_client/sfera/src/model/train_identification.dart'; +import 'package:das_client/sfera/src/model/train_location_information.dart'; import 'package:das_client/sfera/src/model/velocity.dart'; import 'package:das_client/sfera/src/model/virtual_balise.dart'; import 'package:das_client/sfera/src/model/virtual_balise_position.dart'; @@ -200,6 +206,18 @@ class SferaReplyParser { return TrainCharacteristics(type: type, attributes: attributes, children: children, value: value); case TcFeatures.elementType: return TcFeatures(type: type, attributes: attributes, children: children, value: value); + case SferaG2bEventMessage.elementType: + return SferaG2bEventMessage(type: type, attributes: attributes, children: children, value: value); + case G2bEventPayload.elementType: + return G2bEventPayload(type: type, attributes: attributes, children: children, value: value); + case RelatedTrainInformation.elementType: + return RelatedTrainInformation(type: type, attributes: attributes, children: children, value: value); + case OwnTrain.elementType: + return OwnTrain(type: type, attributes: attributes, children: children, value: value); + case TrainLocationInformation.elementType: + return TrainLocationInformation(type: type, attributes: attributes, children: children, value: value); + case Delay.elementType: + return Delay(type: type, attributes: attributes, children: children, value: value); default: return SferaXmlElement(type: type, attributes: attributes, children: children, value: value); } diff --git a/das_client/lib/util/error_code.dart b/das_client/lib/util/error_code.dart index f5b74289..11de18cb 100644 --- a/das_client/lib/util/error_code.dart +++ b/das_client/lib/util/error_code.dart @@ -9,7 +9,7 @@ enum ErrorCode { sferaHandshakeRejected(code: 10001), sferaRequestTimeout(code: 10002), sferaJpUnavailable(code: 10003), - sferaSpInvalid(code: 10004); + sferaInvalid(code: 10004); const ErrorCode({ required this.code, @@ -36,8 +36,8 @@ extension ErrorCodeExtension on ErrorCode { return context.l10n.c_error_sfera_request_timeout; case ErrorCode.sferaJpUnavailable: return context.l10n.c_error_sfera_jp_unavailable; - case ErrorCode.sferaSpInvalid: - return context.l10n.c_error_sfera_sp_invalid; + case ErrorCode.sferaInvalid: + return context.l10n.c_error_sfera_invalid; } } } \ No newline at end of file diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart index 861ec74f..4b34fd86 100644 --- a/das_client/test/sfera/mapper/sfera_mapper_test.dart +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -58,7 +58,8 @@ void main() { trainCharacteristics.add(trainCharacteristic); } - return SferaModelMapper.mapToJourney(journeyProfile, segmentProfiles, trainCharacteristics); + return SferaModelMapper.mapToJourney( + journeyProfile: journeyProfile, segmentProfiles: segmentProfiles, trainCharacteristics: trainCharacteristics); } test('Test invalid journey on SP missing', () async { @@ -200,25 +201,32 @@ void main() { expect(journey.valid, true); expect(journey.metadata.nonStandardTrackEquipmentSegments, hasLength(7)); - expect(journey.metadata.nonStandardTrackEquipmentSegments[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect( + journey.metadata.nonStandardTrackEquipmentSegments[0].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); expect(journey.metadata.nonStandardTrackEquipmentSegments[0].startOrder, isNull); expect(journey.metadata.nonStandardTrackEquipmentSegments[0].endOrder, 1500); - expect(journey.metadata.nonStandardTrackEquipmentSegments[1].type, TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); + expect(journey.metadata.nonStandardTrackEquipmentSegments[1].type, + TrackEquipmentType.etcsL1ls2TracksWithSingleTrackEquipment); expect(journey.metadata.nonStandardTrackEquipmentSegments[1].startOrder, isNull); expect(journey.metadata.nonStandardTrackEquipmentSegments[1].endOrder, 102300); - expect(journey.metadata.nonStandardTrackEquipmentSegments[2].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[2].type, + TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); expect(journey.metadata.nonStandardTrackEquipmentSegments[2].startOrder, 102500); expect(journey.metadata.nonStandardTrackEquipmentSegments[2].endOrder, 103700); - expect(journey.metadata.nonStandardTrackEquipmentSegments[3].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect( + journey.metadata.nonStandardTrackEquipmentSegments[3].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); expect(journey.metadata.nonStandardTrackEquipmentSegments[3].startOrder, 103700); expect(journey.metadata.nonStandardTrackEquipmentSegments[3].endOrder, 307000); - expect(journey.metadata.nonStandardTrackEquipmentSegments[4].type, TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[4].type, + TrackEquipmentType.etcsL2ConvSpeedReversingImpossible); expect(journey.metadata.nonStandardTrackEquipmentSegments[4].startOrder, 307000); expect(journey.metadata.nonStandardTrackEquipmentSegments[4].endOrder, 307800); - expect(journey.metadata.nonStandardTrackEquipmentSegments[5].type, TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); + expect(journey.metadata.nonStandardTrackEquipmentSegments[5].type, + TrackEquipmentType.etcsL2ExtSpeedReversingImpossible); expect(journey.metadata.nonStandardTrackEquipmentSegments[5].startOrder, 409200); expect(journey.metadata.nonStandardTrackEquipmentSegments[5].endOrder, 410200); - expect(journey.metadata.nonStandardTrackEquipmentSegments[6].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); + expect( + journey.metadata.nonStandardTrackEquipmentSegments[6].type, TrackEquipmentType.etcsL2ExtSpeedReversingPossible); expect(journey.metadata.nonStandardTrackEquipmentSegments[6].startOrder, 410200); expect(journey.metadata.nonStandardTrackEquipmentSegments[6].endOrder, isNull); }); @@ -335,7 +343,8 @@ void main() { test('Test protection section is parsed correctly', () async { final journey = getJourney('513', 1); - final protectionSections = journey.data.where((it) => it.type == Datatype.protectionSection).cast().toList(); + final protectionSections = + journey.data.where((it) => it.type == Datatype.protectionSection).cast().toList(); expect(journey.valid, true); expect(protectionSections, hasLength(6)); @@ -355,7 +364,10 @@ void main() { test('Test additional speed restriction is parsed correctly no items between', () async { final journey = getJourney('513', 1); - final speedRestrictions = journey.data.where((it) => it.type == Datatype.additionalSpeedRestriction).cast().toList(); + final speedRestrictions = journey.data + .where((it) => it.type == Datatype.additionalSpeedRestriction) + .cast() + .toList(); expect(journey.valid, true); expect(speedRestrictions, hasLength(1)); @@ -375,7 +387,10 @@ void main() { test('Test additional speed restriction is parsed correctly over multiple segments', () async { final journey = getJourney('500', 3); - final speedRestrictions = journey.data.where((it) => it.type == Datatype.additionalSpeedRestriction).cast().toList(); + final speedRestrictions = journey.data + .where((it) => it.type == Datatype.additionalSpeedRestriction) + .cast() + .toList(); expect(journey.valid, true); expect(speedRestrictions, hasLength(2)); @@ -402,7 +417,10 @@ void main() { test('Test additional speed restriction without a date', () async { final journey = getJourney('513_asp_no_date', 1, spTrainNumber: '513'); - final speedRestrictions = journey.data.where((it) => it.type == Datatype.additionalSpeedRestriction).cast().toList(); + final speedRestrictions = journey.data + .where((it) => it.type == Datatype.additionalSpeedRestriction) + .cast() + .toList(); expect(journey.valid, true); expect(speedRestrictions, hasLength(1)); @@ -422,7 +440,10 @@ void main() { test('Test additional speed restriction with date in the past', () async { final journey = getJourney('513_asp_date_before', 1, spTrainNumber: '513'); - final speedRestrictions = journey.data.where((it) => it.type == Datatype.additionalSpeedRestriction).cast().toList(); + final speedRestrictions = journey.data + .where((it) => it.type == Datatype.additionalSpeedRestriction) + .cast() + .toList(); expect(journey.valid, true); expect(speedRestrictions, hasLength(0)); @@ -431,7 +452,10 @@ void main() { test('Test additional speed restriction with date in the future', () async { final journey = getJourney('513_asp_date_after', 1, spTrainNumber: '513'); - final speedRestrictions = journey.data.where((it) => it.type == Datatype.additionalSpeedRestriction).cast().toList(); + final speedRestrictions = journey.data + .where((it) => it.type == Datatype.additionalSpeedRestriction) + .cast() + .toList(); expect(journey.valid, true); expect(speedRestrictions, hasLength(0)); @@ -468,7 +492,8 @@ void main() { test('Test connection tracks are parsed correctly', () async { final journey = getJourney('9999', 5); - final connectionTracks = journey.data.where((it) => it.type == Datatype.connectionTrack).cast().toList(); + final connectionTracks = + journey.data.where((it) => it.type == Datatype.connectionTrack).cast().toList(); expect(journey.valid, true); expect(connectionTracks, hasLength(3)); 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 7141178a..3722e270 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 @@ -96,7 +96,7 @@ void main() { fail('Test should not call success'); }, (task, errorCode) { expect(task, segmentTask); - expect(errorCode, ErrorCode.sferaSpInvalid); + expect(errorCode, ErrorCode.sferaInvalid); }); verify(mqttService.publishMessage(any, any, any)).called(1); From 5bb0c5800129905b7c46cfde37ea20c150101223 Mon Sep 17 00:00:00 2001 From: u221711 Date: Thu, 19 Dec 2024 14:31:00 +0100 Subject: [PATCH 17/22] chore: fix test --- .../sfera/service/sfera_request_journey_profile_task_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 6a3d4782..7c7101f3 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 @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:das_client/mqtt/mqtt_component.dart'; import 'package:das_client/sfera/sfera_component.dart'; +import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; import 'package:das_client/sfera/src/service/task/request_journey_profile_task.dart'; import 'package:das_client/util/error_code.dart'; @@ -38,7 +39,7 @@ void main() { await journeyTask.execute((task, data) { expect(task, journeyTask); - expect(data, sferaG2bReplyMessage.payload!.journeyProfiles.first); + expect(data!.whereType().first, sferaG2bReplyMessage.payload!.journeyProfiles.first); }, (task, errorCode) { fail('Task failed with error code $errorCode'); }); From 0d9a43289c849496048725779bf23a0d0c1b33a9 Mon Sep 17 00:00:00 2001 From: u221711 Date: Thu, 19 Dec 2024 22:37:48 +0100 Subject: [PATCH 18/22] fix lint --- .../logs/das-log-lastSavedFile.json | 1 + .../event/related_train_information_event_handler.dart | 3 --- das_client/lib/sfera/src/service/task/handshake_task.dart | 8 ++++---- 3 files changed, 5 insertions(+), 7 deletions(-) create mode 100644 das_client/applicationSupportPath/logs/das-log-lastSavedFile.json diff --git a/das_client/applicationSupportPath/logs/das-log-lastSavedFile.json b/das_client/applicationSupportPath/logs/das-log-lastSavedFile.json new file mode 100644 index 00000000..de9aa7a1 --- /dev/null +++ b/das_client/applicationSupportPath/logs/das-log-lastSavedFile.json @@ -0,0 +1 @@ +{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}}, \ No newline at end of file diff --git a/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart b/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart index 5e34db2d..fcddccad 100644 --- a/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart +++ b/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart @@ -1,12 +1,9 @@ -import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/related_train_information.dart'; import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart'; -import 'package:das_client/sfera/src/repo/sfera_repository.dart'; import 'package:das_client/sfera/src/service/event/sfera_event_message_handler.dart'; import 'package:fimber/fimber.dart'; class RelatedTrainInformationEventHandler extends SferaEventMessageHandler { - RelatedTrainInformationEventHandler(super.onMessageHandled); @override 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 2437d7b2..1415dc46 100644 --- a/das_client/lib/sfera/src/service/task/handshake_task.dart +++ b/das_client/lib/sfera/src/service/task/handshake_task.dart @@ -54,15 +54,15 @@ class HandshakeTask extends SferaTask { } @override - Future handleMessage(SferaG2bReplyMessage message) async { - if (message.handshakeAcknowledgement != null) { + Future handleMessage(SferaG2bReplyMessage replyMessage) async { + if (replyMessage.handshakeAcknowledgement != null) { stopTimeout(); Fimber.i('Received handshake acknowledgment'); _taskCompletedCallback(this, null); return true; - } else if (message.handshakeReject != null) { + } else if (replyMessage.handshakeReject != null) { stopTimeout(); - Fimber.w('Received handshake reject with reason=${message.handshakeReject?.handshakeRejectReason?.toString()}'); + Fimber.w('Received handshake reject with reason=${replyMessage.handshakeReject?.handshakeRejectReason?.toString()}'); _taskFailedCallback(this, ErrorCode.sferaHandshakeRejected); _mqttService.disconnect(); return true; From b1ec40937ab2548ace4d04a256579f8334ce2415 Mon Sep 17 00:00:00 2001 From: u221711 Date: Thu, 19 Dec 2024 22:39:23 +0100 Subject: [PATCH 19/22] remove unused file --- .../applicationSupportPath/logs/das-log-lastSavedFile.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 das_client/applicationSupportPath/logs/das-log-lastSavedFile.json diff --git a/das_client/applicationSupportPath/logs/das-log-lastSavedFile.json b/das_client/applicationSupportPath/logs/das-log-lastSavedFile.json deleted file mode 100644 index de9aa7a1..00000000 --- a/das_client/applicationSupportPath/logs/das-log-lastSavedFile.json +++ /dev/null @@ -1 +0,0 @@ -{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}},{"time":1734615039.133,"source":"das_client","message":"Test message","level":"info","metadata":{"version":"0.1","systemName":"unitTests"}}, \ No newline at end of file From 3bb680bbf3ff393a777e20d9b3f9ca02f906c7bc Mon Sep 17 00:00:00 2001 From: "Raphael Schreiber (IT-PTR-CEN1-BDE22-Extern)" Date: Fri, 20 Dec 2024 12:43:37 +0100 Subject: [PATCH 20/22] feat: Fixed the delay model and added all the needed Sfera models. Also added Duration into Metadata and changed the UI so that it only shows the delay from the metadata. Still have to check if it is needed to add hours --- .../widgets/header/time_container.dart | 79 ++++++++++++------- das_client/lib/model/journey/metadata.dart | 2 + .../sfera/src/mapper/sfera_model_mapper.dart | 19 +++++ ...lated_train_information_event_handler.dart | 2 +- das_client/pubspec.lock | 8 ++ das_client/pubspec.yaml | 1 + 6 files changed, 82 insertions(+), 29 deletions(-) diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart b/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart index 20c2caaf..fee24561 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart @@ -1,40 +1,52 @@ -import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart'; +import 'package:das_client/app/bloc/train_journey_cubit.dart'; import 'package:flutter/material.dart'; +import 'package:das_client/model/journey/journey.dart'; +import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart'; class TimeContainer extends StatelessWidget { const TimeContainer({super.key}); @override Widget build(BuildContext context) { - return SBBGroup( - margin: const EdgeInsetsDirectional.fromSTEB( - sbbDefaultSpacing * 0.5, - 0, - sbbDefaultSpacing * 0.5, - sbbDefaultSpacing, - ), - padding: const EdgeInsets.all(sbbDefaultSpacing), - useShadow: false, - child: SizedBox( - width: 124.0, - height: 112.0, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - '05:43:00', - style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0), + final bloc = context.trainJourneyCubit; + + return StreamBuilder( + stream: bloc.journeyStream, + builder: (context, snapshot) { + if (!snapshot.hasData || snapshot.data == null) { + return Center( + child: SBBLoadingIndicator(), + ); + } + final Journey journey = snapshot.data!; + + return SBBGroup( + margin: const EdgeInsetsDirectional.fromSTEB( + sbbDefaultSpacing * 0.5, + 0, + sbbDefaultSpacing * 0.5, + sbbDefaultSpacing, ), - _divider(), - Text( - '+00:01:30', - style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0), + padding: const EdgeInsets.all(sbbDefaultSpacing), + useShadow: false, + child: SizedBox( + width: 124.0, + height: 112.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '05:43:00', + style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0), + ), + _divider(), + _punctualityDisplay(journey), + ], + ), ), - ], - ), - ), - ); + ); + }); } Widget _divider() { @@ -43,4 +55,15 @@ class TimeContainer extends StatelessWidget { child: Divider(height: 1.0, color: SBBColors.cloud), ); } + + Widget _punctualityDisplay(Journey journey) { + final Duration delay = journey.metadata.delay!; + final String minutes = delay.inMinutes.abs().toString(); + final String seconds = (delay.inSeconds.abs() % 60).toString(); + final String formattedDuration = '${delay.isNegative ? '-' : '+'}$minutes:${seconds.padLeft(2, '0')}'; + return Text( + formattedDuration, + style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0), + ); + } } diff --git a/das_client/lib/model/journey/metadata.dart b/das_client/lib/model/journey/metadata.dart index a4be5262..f7302643 100644 --- a/das_client/lib/model/journey/metadata.dart +++ b/das_client/lib/model/journey/metadata.dart @@ -10,6 +10,7 @@ class Metadata { this.currentPosition, this.routeStart, this.routeEnd, + this.delay, this.breakSeries, this.additionalSpeedRestrictions = const [], this.nonStandardTrackEquipmentSegments = const [], @@ -21,6 +22,7 @@ class Metadata { final List additionalSpeedRestrictions; final BaseData? routeStart; final BaseData? routeEnd; + final Duration? delay; final List nonStandardTrackEquipmentSegments; final BreakSeries? breakSeries; final Set availableBreakSeries; diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart index a4b83c75..5bdc329f 100644 --- a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -33,6 +33,7 @@ import 'package:das_client/sfera/src/model/speeds.dart'; import 'package:das_client/sfera/src/model/taf_tap_location.dart'; import 'package:das_client/sfera/src/model/train_characteristics.dart'; import 'package:fimber/fimber.dart'; +import 'package:iso_duration/iso_duration.dart'; class SferaModelMapper { SferaModelMapper._(); @@ -134,6 +135,7 @@ class SferaModelMapper { final trainCharacteristic = _resolveFirstTrainCharacteristics(journeyProfile, trainCharacteristics); final servicePoints = journeyData.where((it) => it.type == Datatype.servicePoint).toList(); + return Journey( metadata: Metadata( nextStop: servicePoints.length > 1 ? servicePoints[1] as ServicePoint : null, @@ -141,6 +143,9 @@ class SferaModelMapper { additionalSpeedRestrictions: additionalSpeedRestrictions, routeStart: journeyData.firstOrNull, routeEnd: journeyData.lastOrNull, + delay: _stringToDuration(relatedTrainInformation == null + ? '+PT0M0S' //Base Value for when the information is Null + : relatedTrainInformation.ownTrain.trainLocationInformation.delay.delay), nonStandardTrackEquipmentSegments: trackEquipmentSegments, availableBreakSeries: _parseAvailableBreakSeries(journeyData), breakSeries: trainCharacteristic?.tcFeatures.trainCategoryCode != null && @@ -154,6 +159,20 @@ class SferaModelMapper { ); } + static Duration _stringToDuration(String stringToChange) { + //'-PT41M30S' + + //TODO code in util auslagern und ganz ganz viele tests wie zb -5 stunden + //TODO anschauen was mit über einer stunde geschehen sollte (mit UX) + final Duration? delay = tryParseIso8601Duration(stringToChange); + + if (delay == null) { + //Todo change the error-code + throw FormatException('Invalid ISO 8601 duration format'); + } + return delay; + } + static List _parseAdditionalSpeedRestrictions( JourneyProfile journeyProfile, List segmentProfiles) { final List result = []; diff --git a/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart b/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart index fcddccad..0171631e 100644 --- a/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart +++ b/das_client/lib/sfera/src/service/event/related_train_information_event_handler.dart @@ -12,7 +12,7 @@ class RelatedTrainInformationEventHandler extends SferaEventMessageHandler Date: Mon, 23 Dec 2024 11:04:24 +0100 Subject: [PATCH 21/22] fix: fixed the mentioned points. Still need to do the tests. SBBLoadingIndicator does not work currently. --- das_client/README.md | 18 ++- .../widgets/header/time_container.dart | 121 ++++++++++-------- .../train_selection/train_selection.dart | 2 +- .../sfera/src/mapper/sfera_model_mapper.dart | 20 +-- das_client/lib/sfera/src/model/delay.dart | 13 ++ .../src/model/train_location_information.dart | 1 - das_client/pubspec.yaml | 1 + 7 files changed, 101 insertions(+), 75 deletions(-) diff --git a/das_client/README.md b/das_client/README.md index f4ebab91..49fec459 100644 --- a/das_client/README.md +++ b/das_client/README.md @@ -35,9 +35,23 @@ fvm flutter test --flavor dev --dart-define=MQTT_USERNAME=${MQTT_USERNAME} --dar ## Architecture -TODO - +### Test file structure +To prevent confusion, fictive train numbers with the prefix `T` are used for the test scenarios. It is desired to create new train journeys for different features. +The file structure in [test_resources](test_resources) for a test scenario looks as follows: +* base directory named `_` +* journey profile named `SFERA_JP__` +* corresponding segment profiles named `SFERA_SP__` +* corresponding train characteristics named `SFERA_TC__` + An example test scenario for train number T1 could look like this: +* T1_demo_journey/ + * SFERA_JP_T1 + * SFERA_JP_T1_without_stop + * SFERA_SP_T1_1 + * SFERA_SP_T1_2 + * SFERA_TC_T1_1 + + ## Localization The app is available in three languages: diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart b/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart index fee24561..a1c6c9e8 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart @@ -1,6 +1,7 @@ import 'package:das_client/app/bloc/train_journey_cubit.dart'; import 'package:flutter/material.dart'; import 'package:das_client/model/journey/journey.dart'; +import 'package:intl/intl.dart'; import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart'; class TimeContainer extends StatelessWidget { @@ -8,62 +9,76 @@ class TimeContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final bloc = context.trainJourneyCubit; + return SBBGroup( + margin: const EdgeInsetsDirectional.fromSTEB( + sbbDefaultSpacing * 0.5, + 0, + sbbDefaultSpacing * 0.5, + sbbDefaultSpacing, + ), + padding: const EdgeInsets.all(sbbDefaultSpacing), + useShadow: false, + child: SizedBox( + width: 124.0, + height: 112.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible(child: _currentTime()), + _divider(), + Flexible(child: _punctualityDisplay(context)), + ], + ), + ), + ); + } +} - return StreamBuilder( - stream: bloc.journeyStream, - builder: (context, snapshot) { - if (!snapshot.hasData || snapshot.data == null) { - return Center( - child: SBBLoadingIndicator(), - ); - } - final Journey journey = snapshot.data!; +Widget _divider() { + return const Padding( + padding: EdgeInsets.symmetric(vertical: sbbDefaultSpacing * 0.5), + child: Divider(height: 1.0, color: SBBColors.cloud), + ); +} + +Widget _punctualityDisplay(BuildContext context) { + final bloc = context.trainJourneyCubit; - return SBBGroup( - margin: const EdgeInsetsDirectional.fromSTEB( - sbbDefaultSpacing * 0.5, - 0, - sbbDefaultSpacing * 0.5, - sbbDefaultSpacing, - ), - padding: const EdgeInsets.all(sbbDefaultSpacing), - useShadow: false, - child: SizedBox( - width: 124.0, - height: 112.0, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - '05:43:00', - style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0), - ), - _divider(), - _punctualityDisplay(journey), - ], - ), - ), + return StreamBuilder( + stream: bloc.journeyStream, + builder: (context, snapshot) { + if (!snapshot.hasData || + snapshot.data == null || + snapshot.data!.metadata.delay == null) { + return Center( + //SBBLoadingIndicator doesn't work here. It is not being showed + child: CircularProgressIndicator(), ); - }); - } + } + final Journey journey = snapshot.data!; - Widget _divider() { - return const Padding( - padding: EdgeInsets.symmetric(vertical: sbbDefaultSpacing * 0.5), - child: Divider(height: 1.0, color: SBBColors.cloud), - ); - } + final Duration delay = journey.metadata.delay!; - Widget _punctualityDisplay(Journey journey) { - final Duration delay = journey.metadata.delay!; - final String minutes = delay.inMinutes.abs().toString(); - final String seconds = (delay.inSeconds.abs() % 60).toString(); - final String formattedDuration = '${delay.isNegative ? '-' : '+'}$minutes:${seconds.padLeft(2, '0')}'; - return Text( - formattedDuration, - style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0), - ); - } + final String minutes = (delay.inMinutes.abs() % 60).toString(); + final String seconds = (delay.inSeconds.abs() % 60).toString(); + final String hours = delay.inHours.abs().toString(); + final String formattedDuration = '${delay.isNegative ? '-' : '+'}$hours:$minutes:${seconds.padLeft(2, '0')}'; + return Text( + formattedDuration, + style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0), + ); + }); +} + +StreamBuilder _currentTime() { + return StreamBuilder( + stream: Stream.periodic(const Duration(seconds: 1)), + builder: (context, snapshot) { + return Text( + DateFormat('HH:mm:ss').format(DateTime.now().toLocal()), + style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0), + ); + }, + ); } 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 ed01fc07..0704a322 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 @@ -66,7 +66,7 @@ class _TrainSelectionState extends State { onChanged: (value) => context.trainJourneyCubit.updateTrainNumber(value), controller: _trainNumberController, labelText: context.l10n.p_train_selection_trainnumber_description, - keyboardType: TextInputType.number, + keyboardType: TextInputType.text, ), ); } diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart index 5bdc329f..bbf3ff9d 100644 --- a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -19,6 +19,7 @@ import 'package:das_client/model/journey/track_equipment.dart'; import 'package:das_client/model/journey/velocity.dart'; import 'package:das_client/model/localized_string.dart'; import 'package:das_client/sfera/src/mapper/track_equipment_mapper.dart'; +import 'package:das_client/sfera/src/model/delay.dart'; import 'package:das_client/sfera/src/model/enums/length_type.dart'; import 'package:das_client/sfera/src/model/enums/start_end_qualifier.dart'; import 'package:das_client/sfera/src/model/enums/stop_skip_pass.dart'; @@ -33,7 +34,6 @@ import 'package:das_client/sfera/src/model/speeds.dart'; import 'package:das_client/sfera/src/model/taf_tap_location.dart'; import 'package:das_client/sfera/src/model/train_characteristics.dart'; import 'package:fimber/fimber.dart'; -import 'package:iso_duration/iso_duration.dart'; class SferaModelMapper { SferaModelMapper._(); @@ -143,9 +143,7 @@ class SferaModelMapper { additionalSpeedRestrictions: additionalSpeedRestrictions, routeStart: journeyData.firstOrNull, routeEnd: journeyData.lastOrNull, - delay: _stringToDuration(relatedTrainInformation == null - ? '+PT0M0S' //Base Value for when the information is Null - : relatedTrainInformation.ownTrain.trainLocationInformation.delay.delay), + delay: Delay.toDuration(relatedTrainInformation?.ownTrain.trainLocationInformation.delay.delay), nonStandardTrackEquipmentSegments: trackEquipmentSegments, availableBreakSeries: _parseAvailableBreakSeries(journeyData), breakSeries: trainCharacteristic?.tcFeatures.trainCategoryCode != null && @@ -159,20 +157,6 @@ class SferaModelMapper { ); } - static Duration _stringToDuration(String stringToChange) { - //'-PT41M30S' - - //TODO code in util auslagern und ganz ganz viele tests wie zb -5 stunden - //TODO anschauen was mit über einer stunde geschehen sollte (mit UX) - final Duration? delay = tryParseIso8601Duration(stringToChange); - - if (delay == null) { - //Todo change the error-code - throw FormatException('Invalid ISO 8601 duration format'); - } - return delay; - } - static List _parseAdditionalSpeedRestrictions( JourneyProfile journeyProfile, List segmentProfiles) { final List result = []; diff --git a/das_client/lib/sfera/src/model/delay.dart b/das_client/lib/sfera/src/model/delay.dart index 308ffff6..2d4cfc00 100644 --- a/das_client/lib/sfera/src/model/delay.dart +++ b/das_client/lib/sfera/src/model/delay.dart @@ -1,4 +1,6 @@ import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:fimber/fimber.dart'; +import 'package:iso_duration/iso_duration.dart'; class Delay extends SferaXmlElement { static const String elementType = 'Delay'; @@ -11,4 +13,15 @@ class Delay extends SferaXmlElement { bool validate() { return validateHasAttribute('Delay') && super.validate(); } + + static Duration? toDuration(String? stringToChange) { + //TODO anschauen, was mit über einer Stunde geschehen sollte (mit UX) + final Duration? delay = tryParseIso8601Duration(stringToChange); + + if (delay == null) { + Fimber.w('Invalid ISO 8601 duration format'); + return null; + } + return delay; + } } diff --git a/das_client/lib/sfera/src/model/train_location_information.dart b/das_client/lib/sfera/src/model/train_location_information.dart index 9568c69e..21e86c97 100644 --- a/das_client/lib/sfera/src/model/train_location_information.dart +++ b/das_client/lib/sfera/src/model/train_location_information.dart @@ -4,7 +4,6 @@ import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; class TrainLocationInformation extends SferaXmlElement { static const String elementType = 'TrainLocationInformation'; - TrainLocationInformation({super.type = elementType, super.attributes, super.children, super.value}); Delay get delay => children.whereType().first; diff --git a/das_client/pubspec.yaml b/das_client/pubspec.yaml index 18de3cdf..c27c54ed 100644 --- a/das_client/pubspec.yaml +++ b/das_client/pubspec.yaml @@ -60,6 +60,7 @@ dependencies: synchronized: ^3.3.0 # https://pub.dev/packages/flutter_svg flutter_svg: ^2.0.14 + # https://pub.dev/packages/iso_duration iso_duration: ^0.1.1 dev_dependencies: From d33253b03512a22f3c1a0d5641d10be25f6217df Mon Sep 17 00:00:00 2001 From: "Raphael Schreiber (IT-PTR-CEN1-BDE22-Extern)" Date: Tue, 14 Jan 2025 10:44:33 +0100 Subject: [PATCH 22/22] fix: fixed the tests and fixed the mentioned points --- .../test/train_journey_test.dart | 127 ++++++++- .../test/train_search_test.dart | 13 +- das_client/ios/Podfile.lock | 2 +- .../widgets/header/time_container.dart | 40 ++- das_client/lib/sfera/src/model/delay.dart | 10 +- .../test/sfera/mapper/sfera_mapper_test.dart | 55 ++++ .../T5_breaking_series/SFERA_JP_T5.xml | 35 --- .../T5_breaking_series/SFERA_SP_T5_1.xml | 252 ------------------ .../T5_breaking_series/SFERA_TC_T5_1.xml | 8 - ...99_1000.xml => SFERA_Event_T9999_2000.xml} | 0 .../SFERA_Event_T9999_60000.xml | 22 ++ 11 files changed, 230 insertions(+), 334 deletions(-) delete mode 100644 sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_JP_T5.xml delete mode 100644 sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_SP_T5_1.xml delete mode 100644 sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_TC_T5_1.xml rename sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/{SFERA_Event_T9999_1000.xml => SFERA_Event_T9999_2000.xml} (100%) create mode 100644 sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/SFERA_Event_T9999_60000.xml diff --git a/das_client/integration_test/test/train_journey_test.dart b/das_client/integration_test/test/train_journey_test.dart index 02fc3c99..26d42e6e 100644 --- a/das_client/integration_test/test/train_journey_test.dart +++ b/das_client/integration_test/test/train_journey_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:das_client/app/pages/journey/train_journey/widgets/header/header.dart'; import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -28,7 +30,7 @@ void main() { await tester.pumpAndSettle(); // check if station is present - expect(find.text('Solothurn'), findsOneWidget); + expect(find.text('Haltestelle B'), findsWidgets); await tester.pumpAndSettle(); }); @@ -37,7 +39,6 @@ void main() { // Load app widget. await prepareAndStartApp(tester); - // final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); expect(trainNumberText, findsOneWidget); @@ -59,4 +60,126 @@ void main() { await tester.pumpAndSettle(); }); }); + + testWidgets('find base value when no punctuality update comes', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); + expect(trainNumberText, findsOneWidget); + + await enterText(tester, trainNumberText, 'T6'); + + final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + await tester.tap(primaryButton); + + // wait for train journey to load + await tester.pumpAndSettle(); + + //find the header and check if it is existent + final headerFinder = find.byType(Header); + expect(headerFinder, findsOneWidget); + + //Find the text in the header + expect(find.descendant(of: headerFinder, matching: find.text('+00:00')), findsOneWidget); + + await tester.pumpAndSettle(); + }); + + testWidgets('check if the displayed current time is correct', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + //Select the correct train number + final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); + expect(trainNumberText, findsOneWidget); + + await enterText(tester, trainNumberText, 'T6'); + + //Log into the journey + final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + await tester.tap(primaryButton); + + // wait for train journey to load + await tester.pumpAndSettle(); + + //find the header and check if it is existent + final headerFinder = find.byType(Header); + expect(headerFinder, findsOneWidget); + + final DateTime currentTime = DateTime.now(); + final String currentHour = currentTime.hour <= 9 ? '0${currentTime.hour}' : (currentTime.hour).toString(); + final String currentMinutes = currentTime.hour <= 9 ? '0${currentTime.minute}' : (currentTime.minute).toString(); + final String currentSeconds = currentTime.hour <= 9 ? '0${currentTime.second}' : (currentTime.second).toString(); + final String nextSecond = + currentTime.hour <= 9 ? '0${currentTime.second + 1}' : (currentTime.second + 1).toString(); + final String currentWholeTime = '$currentHour:$currentMinutes:$currentSeconds'; + final String nextSecondWholeTime = '$currentHour:$currentMinutes:$nextSecond'; + + if (!find.descendant(of: headerFinder, matching: find.text(currentWholeTime)).evaluate().isNotEmpty) { + expect(find.descendant(of: headerFinder, matching: find.text(nextSecondWholeTime)), findsOneWidget); + } else { + expect(find.descendant(of: headerFinder, matching: find.text(currentWholeTime)), findsOneWidget); + } + + await tester.pumpAndSettle(); + }); + + testWidgets('check if update sent is correct', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + // Select the correct train number + final trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); + expect(trainNumberText, findsOneWidget); + + await enterText(tester, trainNumberText, 'T9999'); + + // Log into the journey + final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + await tester.tap(primaryButton); + + // Wait for train journey to load + await tester.pumpAndSettle(); + + // Find the header and check if it is existent + final headerFinder = find.byType(Header); + expect(headerFinder, findsOneWidget); + + // Timer logic: increase timer every second, rerun the base every 100 ms and check if the UI changed + int timer = 0; + const maxTime = 10; + int millisecondsCounter = 0; + + final completer = Completer(); + + expect(find.descendant(of: headerFinder, matching: find.text('+00:00')), findsOneWidget); + + while (!completer.isCompleted) { + await tester.pumpAndSettle(); + + if (!find.descendant(of: headerFinder, matching: find.text('+00:00')).evaluate().isNotEmpty) { + expect(find.descendant(of: headerFinder, matching: find.text('+00:30')), findsOneWidget); + completer.complete(); + break; + } + + millisecondsCounter += 100; + if (millisecondsCounter % 1000 == 0) { + timer++; + } + + if (timer > maxTime) { + completer + .completeError(Exception('UI did not change from the base value to the updated value (+00:00 -> +00:30)')); + break; + } + + await Future.delayed(const Duration(milliseconds: 100)); + } + + await completer.future; + + await tester.pumpAndSettle(); + }); } diff --git a/das_client/integration_test/test/train_search_test.dart b/das_client/integration_test/test/train_search_test.dart index acb52828..5a624094 100644 --- a/das_client/integration_test/test/train_search_test.dart +++ b/das_client/integration_test/test/train_search_test.dart @@ -9,7 +9,6 @@ import '../util/test_utils.dart'; void main() { group('train search screen tests', () { - testWidgets('test default values', (tester) async { // Load app widget. await prepareAndStartApp(tester); @@ -59,7 +58,6 @@ void main() { // check that the primary button is disabled final primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; expect(tester.widget(primaryButton).onPressed, isNull); - }); testWidgets('test can select yesterday', (tester) async { @@ -70,7 +68,8 @@ void main() { final yesterday = today.add(Duration(days: -1)); final todayDateTextFinder = find.text(Format.date(today)); - final yesterdayDateTextFinder = find.text('${Format.date(yesterday)} ${l10n.p_train_selection_date_not_today_warning}'); + final yesterdayDateTextFinder = + find.text('${Format.date(yesterday)} ${l10n.p_train_selection_date_not_today_warning}'); // Verify that today is preselected expect(todayDateTextFinder, findsOneWidget); @@ -90,7 +89,6 @@ void main() { expect(todayDateTextFinder, findsNothing); expect(yesterdayDateTextFinder, findsOneWidget); - }); testWidgets('test can not select day before yesterday', (tester) async { @@ -102,7 +100,8 @@ void main() { final dayBeforeYesterday = today.add(Duration(days: -2)); final todayDateTextFinder = find.text(Format.date(today)); - final yesterdayDateTextFinder = find.text('${Format.date(yesterday)} ${l10n.p_train_selection_date_not_today_warning}'); + final yesterdayDateTextFinder = + find.text('${Format.date(yesterday)} ${l10n.p_train_selection_date_not_today_warning}'); final dayBeforeYesterdayDateTextFinder = find.text(Format.date(dayBeforeYesterday)); // Verify that today is preselected @@ -114,7 +113,8 @@ void main() { final sbbDatePickerFinder = find.byWidgetPredicate((widget) => widget is SBBDatePicker); final yesterdayFinder = find.descendant( of: sbbDatePickerFinder, - matching: find.byWidgetPredicate((widget) => widget is Text && widget.data == '${(dayBeforeYesterday.day)}.')); + matching: + find.byWidgetPredicate((widget) => widget is Text && widget.data == '${(dayBeforeYesterday.day)}.')); await tapElement(tester, yesterdayFinder); // tap outside dialog @@ -149,6 +149,5 @@ void main() { expect(find.text('${ErrorCode.sferaJpUnavailable.code}: ${l10n.c_error_sfera_jp_unavailable}'), findsOneWidget); }); - }); } diff --git a/das_client/ios/Podfile.lock b/das_client/ios/Podfile.lock index 0ef49177..aaafffbc 100644 --- a/das_client/ios/Podfile.lock +++ b/das_client/ios/Podfile.lock @@ -68,4 +68,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: d9dad56c0cd0b4fd8b4fe3034a53fd42a0b990f6 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart b/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart index a1c6c9e8..ef598173 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/header/time_container.dart @@ -46,34 +46,30 @@ Widget _punctualityDisplay(BuildContext context) { final bloc = context.trainJourneyCubit; return StreamBuilder( - stream: bloc.journeyStream, - builder: (context, snapshot) { - if (!snapshot.hasData || - snapshot.data == null || - snapshot.data!.metadata.delay == null) { - return Center( - //SBBLoadingIndicator doesn't work here. It is not being showed - child: CircularProgressIndicator(), - ); - } - final Journey journey = snapshot.data!; + stream: bloc.journeyStream, + builder: (context, snapshot) { + if (!snapshot.hasData || snapshot.data == null || snapshot.data!.metadata.delay == null) { + return Text('+00:00', style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0)); + } + + final Journey journey = snapshot.data!; + final Duration delay = journey.metadata.delay!; - final Duration delay = journey.metadata.delay!; + final String minutes = NumberFormat('00').format(delay.inMinutes.abs() % 60); + final String seconds = NumberFormat('00').format(delay.inSeconds.abs() % 60); + final String formattedDuration = '${delay.isNegative ? '-' : '+'}$minutes:$seconds'; - final String minutes = (delay.inMinutes.abs() % 60).toString(); - final String seconds = (delay.inSeconds.abs() % 60).toString(); - final String hours = delay.inHours.abs().toString(); - final String formattedDuration = '${delay.isNegative ? '-' : '+'}$hours:$minutes:${seconds.padLeft(2, '0')}'; - return Text( - formattedDuration, - style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0), - ); - }); + return Text( + formattedDuration, + style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0), + ); + }, + ); } StreamBuilder _currentTime() { return StreamBuilder( - stream: Stream.periodic(const Duration(seconds: 1)), + stream: Stream.periodic(const Duration(milliseconds: 200)), builder: (context, snapshot) { return Text( DateFormat('HH:mm:ss').format(DateTime.now().toLocal()), diff --git a/das_client/lib/sfera/src/model/delay.dart b/das_client/lib/sfera/src/model/delay.dart index 2d4cfc00..01ba615e 100644 --- a/das_client/lib/sfera/src/model/delay.dart +++ b/das_client/lib/sfera/src/model/delay.dart @@ -1,5 +1,4 @@ import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; -import 'package:fimber/fimber.dart'; import 'package:iso_duration/iso_duration.dart'; class Delay extends SferaXmlElement { @@ -15,13 +14,10 @@ class Delay extends SferaXmlElement { } static Duration? toDuration(String? stringToChange) { - //TODO anschauen, was mit über einer Stunde geschehen sollte (mit UX) - final Duration? delay = tryParseIso8601Duration(stringToChange); - - if (delay == null) { - Fimber.w('Invalid ISO 8601 duration format'); + try { + return tryParseIso8601Duration(stringToChange); + } catch (error) { return null; } - return delay; } } diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart index df2d9458..d5f4be0a 100644 --- a/das_client/test/sfera/mapper/sfera_mapper_test.dart +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -15,6 +15,7 @@ import 'package:das_client/model/journey/track_equipment.dart'; import 'package:das_client/model/journey/train_series.dart'; import 'package:das_client/sfera/sfera_component.dart'; import 'package:das_client/sfera/src/mapper/sfera_model_mapper.dart'; +import 'package:das_client/sfera/src/model/delay.dart'; import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; import 'package:das_client/sfera/src/model/train_characteristics.dart'; @@ -579,4 +580,58 @@ void main() { expect(journey.metadata.breakSeries!.trainSeries, TrainSeries.R); expect(journey.metadata.breakSeries!.breakSeries, 115); }); + + test('Test correct conversion from String to duration with the delay being PT0M25S', () async { + final String delayAsString = 'PT0M25S'; + final Duration? convertedDelay = Delay.toDuration(delayAsString); + expect(convertedDelay, isNotNull); + expect(convertedDelay!.isNegative, false); + expect(convertedDelay.inMinutes, 0); + expect(convertedDelay.inSeconds, 25); + }); + + test('Test correct conversion from String to duration with negative delay', () async { + final String delayAsString = '-PT3M5S'; + final Duration? convertedDelay = Delay.toDuration(delayAsString); + expect(convertedDelay, isNotNull); + expect(convertedDelay!.isNegative, true); + expect(convertedDelay.inMinutes, -3); + expect(convertedDelay.inSeconds, -185); + }); + + test('Test null String conversion to null duration', () async { + final String? delayAsString = null; + final Duration? convertedDelay = Delay.toDuration(delayAsString); + expect(convertedDelay, isNull); + }); + + test('Test empty String conversion to null duration', () async { + final String delayAsString = ''; + final Duration? convertedDelay = Delay.toDuration(delayAsString); + expect(convertedDelay, isNull); + }); + + test('Test big delay String over one hour conversion to correct duration', () async { + final String delayAsString = 'PT5H45M20S'; + final Duration? convertedDelay = Delay.toDuration(delayAsString); + expect(convertedDelay, isNotNull); + expect(convertedDelay!.isNegative, false); + expect(convertedDelay.inHours, 5); + expect(convertedDelay.inMinutes, 345); + expect(convertedDelay.inSeconds, 20720); + }); + + test('Test only seconds conversion to correct duration', () async { + final String delayAsString = 'PT14S'; + final Duration? convertedDelay = Delay.toDuration(delayAsString); + expect(convertedDelay, isNotNull); + expect(convertedDelay!.isNegative, false); + expect(convertedDelay.inSeconds, 14); + }); + + test('Test wrong ISO 8601 format String conversion to null duration', () async { + final String delayAsString = '+PTH45S3434M334'; + final Duration? convertedDelay = Delay.toDuration(delayAsString); + expect(convertedDelay, isNull); + }); } diff --git a/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_JP_T5.xml b/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_JP_T5.xml deleted file mode 100644 index 59c002c1..00000000 --- a/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_JP_T5.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - 1085 - T5 - 2022-01-04 - - - - - 0085 - - - - - - - - - - - - - - - - - - 1185 - - - diff --git a/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_SP_T5_1.xml b/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_SP_T5_1.xml deleted file mode 100644 index 778f64ca..00000000 --- a/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_SP_T5_1.xml +++ /dev/null @@ -1,252 +0,0 @@ - - - - 0085 - - - - - - CH - 3002 - - - - - - - CH - 3003 - - - - - - - CH - 3004 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CH - 3002 - - - - - - - - - - CH - 3003 - - - - - - - - - - CH - 3004 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_TC_T5_1.xml b/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_TC_T5_1.xml deleted file mode 100644 index 51ce7bf2..00000000 --- a/sfera-mock/src/main/resources/static_sfera_resources/T5_breaking_series/SFERA_TC_T5_1.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - 1185 - - - diff --git a/sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/SFERA_Event_T9999_1000.xml b/sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/SFERA_Event_T9999_2000.xml similarity index 100% rename from sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/SFERA_Event_T9999_1000.xml rename to sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/SFERA_Event_T9999_2000.xml diff --git a/sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/SFERA_Event_T9999_60000.xml b/sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/SFERA_Event_T9999_60000.xml new file mode 100644 index 00000000..20241ef8 --- /dev/null +++ b/sfera-mock/src/main/resources/static_sfera_resources/T9999_mixed_journey/SFERA_Event_T9999_60000.xml @@ -0,0 +1,22 @@ + + + + + + + 1085 + T9999 + 2022-01-04 + + + + + + 0085 + + + + + + +