diff --git a/das_client/assets/icons/icon_header_stop.svg b/das_client/assets/icons/icon_header_stop.svg new file mode 100644 index 00000000..d4d94ab9 --- /dev/null +++ b/das_client/assets/icons/icon_header_stop.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/das_client/assets/icons/icon_stop_on_request.svg b/das_client/assets/icons/icon_stop_on_request.svg new file mode 100644 index 00000000..a488e00e --- /dev/null +++ b/das_client/assets/icons/icon_stop_on_request.svg @@ -0,0 +1,15 @@ + + + + + + + + + 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 72d179ad..4ef7fbaf 100644 --- a/das_client/integration_test/test/train_journey_table_test.dart +++ b/das_client/integration_test/test/train_journey_table_test.dart @@ -1,3 +1,6 @@ +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/service_point_row.dart'; import 'package:das_client/app/pages/profile/profile_page.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; @@ -40,24 +43,20 @@ void main() { expect(scrollableFinder, findsOneWidget); // check first train station - expect(find.text('ZUE'), findsOneWidget); + expect(find.text('Zürich HB'), findsOneWidget); // Scroll to last train station - await tester.dragUntilVisible( - find.text('AAR'), - find.byType(ListView), - const Offset(0, -300) - ); + await tester.dragUntilVisible(find.text('Aarau'), find.byType(ListView), const Offset(0, -300)); }); - testWidgets('test fahrbild stays loaded after navigation', (tester) async { + testWidgets('test if train journey stays loaded after navigation', (tester) async { await prepareAndStartApp(tester); // load train journey by filling out train selection page await _loadTrainJourney(tester, trainNumber: '4816'); // check first train station - expect(find.text('ZUE'), findsOneWidget); + expect(find.text('Zürich HB'), findsOneWidget); await openDrawer(tester); await tapElement(tester, find.text(l10n.w_navigation_drawer_profile_title)); @@ -69,7 +68,109 @@ void main() { await tapElement(tester, find.text(l10n.w_navigation_drawer_fahrtinfo_title)); // check first train station is still visible - expect(find.text('ZUE'), findsOneWidget); + expect(find.text('Zürich HB'), findsOneWidget); + }); + + testWidgets('test both kilometres are displayed', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: '4816'); + + final scrollableFinder = find.byType(ListView); + expect(scrollableFinder, findsOneWidget); + + final hardbruckeRow = findDASTableRowByText('Hardbrücke'); + expect(hardbruckeRow, findsOneWidget); + expect(find.descendant(of: hardbruckeRow, matching: find.text('1.9')), findsOneWidget); + expect(find.descendant(of: hardbruckeRow, matching: find.text('23.5')), findsOneWidget); + }); + + testWidgets('test bracket stations is displayed correctly', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: '9999'); + + final bracketStationD = findDASTableRowByText('Klammerbahnhof D'); + final bracketStationD1 = findDASTableRowByText('Klammerbahnhof D1'); + expect(bracketStationD, findsOneWidget); + expect(bracketStationD1, findsOneWidget); + + // check if the bracket station widget is displayed + final bracketStationDWidget = + find.descendant(of: bracketStationD, matching: find.byKey(BracketStationBody.bracketStationKey)); + final bracketStationD1Widget = + find.descendant(of: bracketStationD1, matching: find.byKey(BracketStationBody.bracketStationKey)); + expect(bracketStationDWidget, findsOneWidget); + expect(bracketStationD1Widget, findsOneWidget); + + // check that the abbreviation is displayed correctly + expect(find.descendant(of: bracketStationDWidget, matching: find.text('D')), findsNothing); + expect(find.descendant(of: bracketStationD1Widget, matching: find.text('D')), findsOneWidget); + }); + + testWidgets('test halt on request is displayed correctly', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: '9999'); + + final stopOnDemandRow = findDASTableRowByText('Halt auf Verlangen C'); + expect(stopOnDemandRow, findsOneWidget); + + final stopOnRequestIcon = + find.descendant(of: stopOnDemandRow, matching: find.byKey(ServicePointRow.stopOnRequestKey)); + expect(stopOnRequestIcon, findsOneWidget); + + final stopOnRequestRoute = + find.descendant(of: stopOnDemandRow, matching: find.byKey(RouteCellBody.stopOnRequestKey)); + final stopRoute = find.descendant(of: stopOnDemandRow, matching: find.byKey(RouteCellBody.stopKey)); + expect(stopOnRequestRoute, findsOneWidget); + expect(stopRoute, findsNothing); + }); + + testWidgets('test route is displayed correctly', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: '9999'); + + final stopRouteRow = findDASTableRowByText('Bahnhof A'); + final nonStoppingPassRouteRow = findDASTableRowByText('Haltestelle B'); + expect(stopRouteRow, findsOneWidget); + expect(nonStoppingPassRouteRow, findsOneWidget); + + // check stop circles + final stopRoute = find.descendant(of: stopRouteRow, matching: find.byKey(RouteCellBody.stopKey)); + final nonStoppingPassRoute = find.descendant(of: nonStoppingPassRouteRow, matching: find.byKey(RouteCellBody.stopKey)); + expect(stopRoute, findsOneWidget); + expect(nonStoppingPassRoute, findsNothing); + + // check route start + final startStationRow = findDASTableRowByText('Bahnhof A'); + final routeStart = find.descendant(of: startStationRow, matching: find.byKey(RouteCellBody.routeStartKey)); + expect(routeStart, findsOneWidget); + + // check route end + final endStationRow = findDASTableRowByText('Klammerbahnhof D1'); + final routeEnd = find.descendant(of: endStationRow, matching: find.byKey(RouteCellBody.routeEndKey)); + expect(routeEnd, findsOneWidget); + }); + + testWidgets('test halt is displayed italic', (tester) async { + await prepareAndStartApp(tester); + + // load train journey by filling out train selection page + await _loadTrainJourney(tester, trainNumber: '4816'); + + final glanzenbergText = find + .byWidgetPredicate((it) => it is Text && it.data == 'Glanzenberg' && it.style?.fontStyle == FontStyle.italic); + expect(glanzenbergText, findsOneWidget); + + final schlierenText = find + .byWidgetPredicate((it) => it is Text && it.data == 'Schlieren' && it.style?.fontStyle != FontStyle.italic); + expect(schlierenText, findsOneWidget); }); }); } diff --git a/das_client/integration_test/test/train_journey_test.dart b/das_client/integration_test/test/train_journey_test.dart index ba5a87b8..6ffe7d28 100644 --- a/das_client/integration_test/test/train_journey_test.dart +++ b/das_client/integration_test/test/train_journey_test.dart @@ -26,7 +26,7 @@ void main() { await tester.pumpAndSettle(); // check if station is present - expect(find.text('SO_W'), findsOneWidget); + expect(find.text('Solothurn'), findsOneWidget); await tester.pumpAndSettle(); }); diff --git a/das_client/integration_test/util/test_utils.dart b/das_client/integration_test/util/test_utils.dart index 7bffcf25..11f6cf72 100644 --- a/das_client/integration_test/util/test_utils.dart +++ b/das_client/integration_test/util/test_utils.dart @@ -1,3 +1,4 @@ +import 'package:das_client/app/widgets/table/das_table.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -24,3 +25,7 @@ Finder findTextFieldByLabel(String label) { final sbbTextField = find.byWidgetPredicate((widget) => widget is SBBTextField && widget.labelText == label); return find.descendant(of: sbbTextField, matching: find.byType(TextField)); } + +Finder findDASTableRowByText(String text) { + return find.ancestor(of: find.text(text), matching: find.byKey(DASTable.rowKey)); +} diff --git a/das_client/lib/app/bloc/train_journey_cubit.dart b/das_client/lib/app/bloc/train_journey_cubit.dart index 97591cc0..a9988c0e 100644 --- a/das_client/lib/app/bloc/train_journey_cubit.dart +++ b/das_client/lib/app/bloc/train_journey_cubit.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:das_client/app/model/ru.dart'; +import 'package:das_client/model/journey/journey.dart'; import 'package:das_client/sfera/sfera_component.dart'; import 'package:das_client/util/error_code.dart'; import 'package:fimber/fimber.dart'; @@ -17,9 +18,7 @@ class TrainJourneyCubit extends Cubit { final SferaService _sferaService; - Stream get journeyStream => _sferaService.journeyStream; - - Stream> get segmentStream => _sferaService.segmentStream; + Stream get journeyStream => _sferaService.journeyStream; StreamSubscription? _stateSubscription; diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart b/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart index 01bf96c8..5b9f4c91 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart @@ -1,9 +1,11 @@ import 'package:das_client/app/i18n/i18n.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/header/departure_authorization.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/header/radio_channel.dart'; +import 'package:das_client/app/widgets/assets.dart'; import 'package:das_client/app/widgets/widget_extensions.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; class MainContainer extends StatelessWidget { const MainContainer({super.key}); @@ -56,8 +58,7 @@ class MainContainer extends StatelessWidget { height: 48.0, child: Row( children: [ - // TODO: Replace with custom icon from figma - const Icon(SBBIcons.route_circle_end_small), + SvgPicture.asset(AppAssets.iconHeaderStop), Expanded( child: Padding( padding: const EdgeInsets.only(left: sbbDefaultSpacing * 0.5), @@ -71,27 +72,25 @@ class MainContainer extends StatelessWidget { } Widget _buttonArea() { - return Builder( - builder: (context) { - return Row( - children: [ - SBBTertiaryButtonLarge( - label: context.l10n.p_train_journey_header_button_dark_theme, - icon: SBBIcons.moon_small, - onPressed: () {}, - ), - SBBTertiaryButtonLarge( - label: context.l10n.p_train_journey_header_button_pause, - icon: SBBIcons.pause_small, - onPressed: () {}, - ), - SBBIconButtonLarge( - icon: SBBIcons.context_menu_small, - onPressed: () {}, - ), - ].withSpacing(width: sbbDefaultSpacing * 0.5), - ); - } - ); + return Builder(builder: (context) { + return Row( + children: [ + SBBTertiaryButtonLarge( + label: context.l10n.p_train_journey_header_button_dark_theme, + icon: SBBIcons.moon_small, + onPressed: () {}, + ), + SBBTertiaryButtonLarge( + label: context.l10n.p_train_journey_header_button_pause, + icon: SBBIcons.pause_small, + onPressed: () {}, + ), + SBBIconButtonLarge( + icon: SBBIcons.context_menu_small, + onPressed: () {}, + ), + ].withSpacing(width: sbbDefaultSpacing * 0.5), + ); + }); } } diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/table/base_row_builder.dart b/das_client/lib/app/pages/journey/train_journey/widgets/table/base_row_builder.dart index 0af81b82..c874233e 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 @@ -10,13 +10,11 @@ abstract class BaseRowBuilder extends DASTableRowBuilder { this.defaultAlignment = Alignment.centerLeft, this.rowColor, this.isCurrentPosition = false, - this.isServicePointStop = false, }); - final double? kilometre; + final List? kilometre; final Alignment defaultAlignment; final Color? rowColor; - final bool isServicePointStop; final bool isCurrentPosition; @override @@ -41,13 +39,20 @@ abstract class BaseRowBuilder extends DASTableRowBuilder { } DASTableCell kilometreCell() { - if (kilometre == null) { + if (kilometre == null || kilometre!.isEmpty) { return DASTableCell.empty(); } - var kilometreAsString = kilometre!.toStringAsFixed(3); - kilometreAsString = kilometreAsString.replaceAll(RegExp(r'0*$'), ''); - return DASTableCell(child: Text(kilometreAsString), alignment: defaultAlignment); + return DASTableCell( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(kilometre![0].toStringAsFixed(1)), + if (kilometre!.length > 1) Text(kilometre![1].toStringAsFixed(1)) + ], + ), + alignment: Alignment.centerLeft); } DASTableCell timeCell() { @@ -58,10 +63,7 @@ abstract class BaseRowBuilder extends DASTableRowBuilder { return DASTableCell( padding: EdgeInsets.all(0.0), alignment: null, - child: RouteCellBody( - showCircle: isServicePointStop, - showChevron: isCurrentPosition, - ), + child: RouteCellBody(isCurrentPosition: isCurrentPosition), ); } 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 new file mode 100644 index 00000000..869e42ee --- /dev/null +++ b/das_client/lib/app/pages/journey/train_journey/widgets/table/cells/bracket_station_body.dart @@ -0,0 +1,46 @@ +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 { + static const Key bracketStationKey = Key('bracketStationKey'); + static const double _bracketStationWidth = 16.0; + static const double _bracketStationFontSize = 12.0; + + const BracketStationBody({ + super.key, + required this.bracketStation, + required this.height + }); + + final BracketStation bracketStation; + final double height; + + @override + Widget build(BuildContext context) { + return Positioned( + top: -sbbDefaultSpacing, + bottom: -sbbDefaultSpacing, + right: 0, + child: Container( + key: bracketStationKey, + height: height, + width: _bracketStationWidth, + color: SBBColors.black, + child: Align( + alignment: Alignment.center, + child: RotatedBox( + quarterTurns: -1, + child: Text( + bracketStation.mainStationAbbreviation ?? '', + style: SBBTextStyles.extraSmallBold.copyWith( + 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 6563cec1..c195b6e1 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 @@ -1,15 +1,24 @@ +import 'package:das_client/app/widgets/table/das_table_theme.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; class RouteCellBody extends StatelessWidget { + static const Key stopKey = Key('stopRouteCell'); + static const Key stopOnRequestKey = Key('stopOnRequestRouteCell'); + static const Key routeStartKey = Key('startRouteCell'); + static const Key routeEndKey = Key('endRouteCell'); + const RouteCellBody({ super.key, this.chevronHeight = 8.0, this.chevronWidth = 16.0, this.circleSize = 14.0, this.lineThickness = 2.0, - this.showCircle = false, - this.showChevron = false, + this.isStop = false, + this.isStopOnRequest = false, + this.isCurrentPosition = false, + this.isRouteStart = false, + this.isRouteEnd = false, }); final double chevronHeight; @@ -17,28 +26,37 @@ class RouteCellBody extends StatelessWidget { final double circleSize; final double lineThickness; - final bool showChevron; - final bool showCircle; + final bool isCurrentPosition; + final bool isStop; + final bool isStopOnRequest; + final bool isRouteStart; + final bool isRouteEnd; @override Widget build(BuildContext context) { - return Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - if (showChevron) _chevron(context), - if (showCircle) _circle(context), - _routeLine(context), - ], + return LayoutBuilder( + builder: (context, constraints) { + final height = constraints.maxHeight; + return Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + _routeLine(context, height), + if (isCurrentPosition) _chevron(context), + if (isStop) _circle(context), + ], + ); + }, ); } - Positioned _routeLine(BuildContext context) { + Positioned _routeLine(BuildContext context, double height) { final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; final lineColor = isDarkTheme ? SBBColors.white : SBBColors.black; return Positioned( - top: -sbbDefaultSpacing, - bottom: -sbbDefaultSpacing, + key: isRouteStart ? routeStartKey : isRouteEnd ? routeEndKey : null, + top: isRouteStart ? height - sbbDefaultSpacing : -sbbDefaultSpacing, + bottom: isRouteEnd ? sbbDefaultSpacing : -sbbDefaultSpacing, right: 0, left: 0, child: VerticalDivider(thickness: lineThickness, color: lineColor), @@ -50,14 +68,7 @@ class RouteCellBody extends StatelessWidget { final circleColor = isDarkTheme ? SBBColors.sky : SBBColors.black; return Positioned( bottom: sbbDefaultSpacing, - child: Container( - width: circleSize, - height: circleSize, - decoration: BoxDecoration( - color: circleColor, - shape: BoxShape.circle, - ), - ), + child: _RouteCircle(size: circleSize, color: circleColor, isStopOnRequest: isStopOnRequest), ); } @@ -65,7 +76,7 @@ class RouteCellBody extends StatelessWidget { final isDarkTheme = SBBBaseStyle.of(context).brightness == Brightness.dark; final chevronColor = isDarkTheme ? SBBColors.sky : SBBColors.black; return Positioned( - bottom: showCircle ? sbbDefaultSpacing + circleSize : sbbDefaultSpacing, + bottom: isStop ? sbbDefaultSpacing + circleSize : sbbDefaultSpacing, child: CustomPaint( size: Size(chevronWidth, chevronHeight), painter: _ChevronPainter(color: chevronColor), @@ -74,6 +85,40 @@ class RouteCellBody extends StatelessWidget { } } +class _RouteCircle extends StatelessWidget { + const _RouteCircle({ + required this.size, + required this.color, + this.isStopOnRequest = false, + }); + + final bool isStopOnRequest; + final double size; + final Color color; + + @override + Widget build(BuildContext context) { + final tableThemeData = DASTableTheme.of(context)?.data; + final tableBackgroundColor = tableThemeData?.backgroundColor ?? SBBColors.white; + return Container( + key: isStopOnRequest ? RouteCellBody.stopOnRequestKey : RouteCellBody.stopKey, + width: size, + height: size, + decoration: isStopOnRequest ? _stopOnRequestDecoration(backgroundColor: tableBackgroundColor) : _stopDecoration(), + ); + } + + BoxDecoration _stopDecoration() => BoxDecoration(color: color, shape: BoxShape.circle); + + BoxDecoration _stopOnRequestDecoration({required Color backgroundColor}) => BoxDecoration( + color: backgroundColor, + shape: BoxShape.circle, + border: Border.all( + color: color, // Set the border color + width: 2.0, // Set the border width + )); +} + class _ChevronPainter extends CustomPainter { _ChevronPainter({this.color = SBBColors.black}); 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 79f3113e..87edfd3a 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,42 +1,93 @@ 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/route_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/sfera/sfera_component.dart'; +import 'package:das_client/model/journey/metadata.dart'; +import 'package:das_client/model/journey/service_point.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; // TODO: Extract real values from SFERA objects. class ServicePointRow extends BaseRowBuilder { + static const Key stopOnRequestKey = Key('stop_on_request_key'); + ServicePointRow({ super.height = 64.0, super.defaultAlignment = _defaultAlignment, - super.kilometre, - super.isCurrentPosition, - super.isServicePointStop, - this.timingPoint, - this.timingPointConstraints, - bool nextStop = false, + this.isRouteStart = false, + this.isRouteEnd = false, + required this.metadata, + required this.servicePoint, }) : super( - rowColor: nextStop ? SBBColors.royal.withOpacity(0.2) : Colors.transparent, - ); + rowColor: metadata.nextStop == servicePoint ? SBBColors.royal.withOpacity(0.2) : Colors.transparent, + kilometre: servicePoint.kilometre, + isCurrentPosition: metadata.currentPosition == servicePoint); - final TimingPoint? timingPoint; - final TimingPointConstraints? timingPointConstraints; + final Metadata metadata; + final ServicePoint servicePoint; static const Alignment _defaultAlignment = Alignment.bottomCenter; + final bool isRouteStart; + final bool isRouteEnd; + @override DASTableCell informationCell() { - final servicePointName = timingPoint?.names.first.name ?? 'Unknown'; + final servicePointName = servicePoint.name.localized; + final textStyle = servicePoint.isStation + ? SBBTextStyles.largeBold.copyWith(fontSize: 24.0) + : SBBTextStyles.largeLight.copyWith(fontSize: 24.0, fontStyle: FontStyle.italic); return DASTableCell( alignment: _defaultAlignment, child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text(servicePointName, style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0)), + Text(servicePointName, style: textStyle), Spacer(), Text('B12'), ], ), ); } + + @override + DASTableCell iconsCell1() { + return DASTableCell( + padding: EdgeInsets.fromLTRB(0, sbbDefaultSpacing * 0.5, 0, sbbDefaultSpacing * 0.5), + child: Stack( + clipBehavior: Clip.none, + children: [ + if (servicePoint.bracketStation != null) + BracketStationBody( + bracketStation: servicePoint.bracketStation!, + height: height!, + ), + if (!servicePoint.mandatoryStop) + Align( + alignment: Alignment.bottomCenter, + child: SvgPicture.asset( + AppAssets.iconStopOnRequest, + key: stopOnRequestKey, + )) + ], + ), + ); + } + + @override + DASTableCell routeCell() { + return DASTableCell( + padding: EdgeInsets.all(0.0), + alignment: null, + child: RouteCellBody( + isStop: servicePoint.isStop, + isCurrentPosition: isCurrentPosition, + isRouteStart: isRouteStart, + isRouteEnd: isRouteEnd, + isStopOnRequest: !servicePoint.mandatoryStop, + ), + ); + } } 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 bbe7c764..46d4bbba 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 @@ -3,10 +3,11 @@ import 'package:das_client/app/i18n/i18n.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/table/service_point_row.dart'; import 'package:das_client/app/widgets/table/das_table.dart'; import 'package:das_client/app/widgets/table/das_table_column.dart'; -import 'package:das_client/sfera/sfera_component.dart'; +import 'package:das_client/model/journey/datatype.dart'; +import 'package:das_client/model/journey/journey.dart'; +import 'package:das_client/model/journey/service_point.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; class TrainJourney extends StatelessWidget { const TrainJourney({super.key}); @@ -15,49 +16,40 @@ class TrainJourney extends StatelessWidget { Widget build(BuildContext context) { final bloc = context.trainJourneyCubit; - return StreamBuilder>( - stream: CombineLatestStream.list([bloc.journeyStream, bloc.segmentStream]), + return StreamBuilder( + stream: bloc.journeyStream, builder: (context, snapshot) { - final JourneyProfile? journeyProfile = snapshot.data?[0]; - final List segmentProfiles = snapshot.data?[1] ?? []; - if (journeyProfile == null) { + final Journey? journey = snapshot.data; + if (journey == null) { return Container(); } - return _body(context, journeyProfile, segmentProfiles); + return _body(context, journey); }, ); } Widget _body( BuildContext context, - JourneyProfile journeyProfile, - List segmentProfiles, + Journey journey, ) { - final timingPoints = journeyProfile.segmentProfilesLists - .expand((it) => it.timingPoints.toList().sublist(it == journeyProfile.segmentProfilesLists.first ? 0 : 1)) - .toList(); - - final points = segmentProfiles.expand((it) => it.points?.timingPoints.toList() ?? []); - return Padding( padding: const EdgeInsets.symmetric(horizontal: sbbDefaultSpacing * 0.5), child: DASTable( columns: _columns(context), rows: [ - ...List.generate(timingPoints.length, (index) { - final timingPoint = timingPoints[index]; - final tpId = timingPoint.timingPointReference.children.whereType().firstOrNull?.tpId; - final tp = points.where((point) => point.id == tpId).firstOrNull; + ...List.generate(journey.data.length, (index) { + final rowData = journey.data[index]; - return ServicePointRow( - kilometre: 10.2, - timingPoint: tp, - timingPointConstraints: timingPoint, - nextStop: index == 1, - isServicePointStop: index != 3 && index != 4, - isCurrentPosition: index == 0 || index == 3, - ).build(context); + switch (rowData.type) { + case Datatype.servicePoint: + return ServicePointRow( + metadata: journey.metadata, + servicePoint: rowData as ServicePoint, + isRouteStart: index == 0, + isRouteEnd: index == journey.data.length - 1, + ).build(context); + } }) ], ), diff --git a/das_client/lib/app/widgets/assets.dart b/das_client/lib/app/widgets/assets.dart new file mode 100644 index 00000000..96509497 --- /dev/null +++ b/das_client/lib/app/widgets/assets.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +@immutable +class AppAssets { + const AppAssets._(); + + static const String _dir = 'assets'; + static const String _iconsDir = '$_dir/icons'; + + static const iconHeaderStop = '$_iconsDir/icon_header_stop.svg'; + static const iconStopOnRequest = '$_iconsDir/icon_stop_on_request.svg'; +} diff --git a/das_client/lib/app/widgets/table/das_table.dart b/das_client/lib/app/widgets/table/das_table.dart index ee9ef72a..b07cf77d 100644 --- a/das_client/lib/app/widgets/table/das_table.dart +++ b/das_client/lib/app/widgets/table/das_table.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:collection/collection.dart'; import 'package:das_client/app/widgets/table/das_table_cell.dart'; import 'package:das_client/app/widgets/table/das_table_column.dart'; @@ -11,6 +13,8 @@ import 'package:flutter/material.dart'; /// The [columns] parameter must not be empty, and all rows must have the same number of cells as columns. @immutable class DASTable extends StatelessWidget { + static const Key rowKey = Key('DAS-Table-row'); + DASTable({ super.key, required this.columns, @@ -140,6 +144,8 @@ class DASTable extends StatelessWidget { return Builder(builder: (context) { final tableThemeData = DASTableTheme.of(context)?.data; final effectiveAlignment = cell.alignment ?? column.alignment; + final BoxBorder? cellBorder = + cell.border ?? column.border ?? tableThemeData?.tableBorder?.toBoxBorder(isLastCell: isLast); return _TableCellWrapper( expanded: column.expanded, width: column.width, @@ -147,10 +153,11 @@ class DASTable extends StatelessWidget { onTap: cell.onTap, child: Container( decoration: BoxDecoration( - border: cell.border ?? column.border ?? tableThemeData?.tableBorder?.toBoxBorder(isLastCell: isLast), + border: cellBorder, color: cell.color ?? row.color ?? column.color ?? tableThemeData?.dataRowColor, ), - padding: cell.padding ?? column.padding ?? EdgeInsets.all(sbbDefaultSpacing * 0.5), + padding: _adjustPaddingToBorder( + cell.padding ?? column.padding ?? EdgeInsets.all(sbbDefaultSpacing * 0.5), cellBorder), clipBehavior: cell.clipBehaviour, child: DefaultTextStyle( style: DefaultTextStyle.of(context).style.merge(tableThemeData?.dataTextStyle), @@ -161,6 +168,18 @@ class DASTable extends StatelessWidget { ); }); } + + EdgeInsets? _adjustPaddingToBorder(EdgeInsets? padding, BoxBorder? border) { + if (padding == null || border == null) { + return padding; + } + + final borderSideStart = border is BorderDirectional ? border.start : (border as Border).left; + final borderSideEnd = border is BorderDirectional ? border.end : (border as Border).right; + + return EdgeInsets.fromLTRB(max(padding.left - borderSideStart.width, 0), max(padding.top - border.top.width, 0), + max(padding.right - borderSideEnd.width, 0), max(padding.bottom - border.bottom.width, 0)); + } } extension _TableBorderExtension on TableBorder { @@ -180,7 +199,7 @@ class _FlexibleHeightRow extends StatelessWidget { @override Widget build(BuildContext context) { - final row = Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: children); + final row = Row(key: DASTable.rowKey, crossAxisAlignment: CrossAxisAlignment.stretch, children: children); if (fixedHeight != null) { return SizedBox(height: fixedHeight, child: row); } diff --git a/das_client/lib/app/widgets/table/das_table_cell.dart b/das_client/lib/app/widgets/table/das_table_cell.dart index 99bc78cc..8e065f6d 100644 --- a/das_client/lib/app/widgets/table/das_table_cell.dart +++ b/das_client/lib/app/widgets/table/das_table_cell.dart @@ -24,7 +24,7 @@ class DASTableCell { final Widget child; final VoidCallback? onTap; final Color? color; - final EdgeInsetsGeometry? padding; + final EdgeInsets? padding; final Clip clipBehaviour; /// If provided, wraps child in Align widget. Can also be defined in [DASTableColumn] diff --git a/das_client/lib/app/widgets/table/das_table_column.dart b/das_client/lib/app/widgets/table/das_table_column.dart index bf5e9796..0af7e041 100644 --- a/das_client/lib/app/widgets/table/das_table_column.dart +++ b/das_client/lib/app/widgets/table/das_table_column.dart @@ -28,7 +28,7 @@ class DASTableColumn { /// The background color for the heading and data cells final Color? color; - final EdgeInsetsGeometry? padding; + final EdgeInsets? padding; /// Whether the column should expand to fill available space. final bool expanded; diff --git a/das_client/lib/model/journey/base_data.dart b/das_client/lib/model/journey/base_data.dart new file mode 100644 index 00000000..8035118c --- /dev/null +++ b/das_client/lib/model/journey/base_data.dart @@ -0,0 +1,9 @@ +import 'package:das_client/model/journey/datatype.dart'; + +abstract class BaseData { + BaseData({required this.type, required this.order, required this.kilometre}); + + final Datatype type; + final int order; + final List kilometre; +} diff --git a/das_client/lib/model/journey/bracket_station.dart b/das_client/lib/model/journey/bracket_station.dart new file mode 100644 index 00000000..3a3ccada --- /dev/null +++ b/das_client/lib/model/journey/bracket_station.dart @@ -0,0 +1,7 @@ + + +class BracketStation { + BracketStation({this.mainStationAbbreviation}); + + final String? mainStationAbbreviation; +} diff --git a/das_client/lib/model/journey/datatype.dart b/das_client/lib/model/journey/datatype.dart new file mode 100644 index 00000000..4ceefff2 --- /dev/null +++ b/das_client/lib/model/journey/datatype.dart @@ -0,0 +1,3 @@ +enum Datatype { + servicePoint; +} diff --git a/das_client/lib/model/journey/journey.dart b/das_client/lib/model/journey/journey.dart new file mode 100644 index 00000000..3219d4fd --- /dev/null +++ b/das_client/lib/model/journey/journey.dart @@ -0,0 +1,13 @@ +import 'package:das_client/model/journey/base_data.dart'; +import 'package:das_client/model/journey/metadata.dart'; + +class Journey { + const Journey({required this.metadata, required this.data, this.valid = true}); + + final Metadata metadata; + final List data; + final bool valid; + + Journey.invalid({Metadata? metadata, List? data}) + : this(metadata: metadata ?? Metadata(), data: data ?? [], valid: false); +} diff --git a/das_client/lib/model/journey/metadata.dart b/das_client/lib/model/journey/metadata.dart new file mode 100644 index 00000000..949a3fb2 --- /dev/null +++ b/das_client/lib/model/journey/metadata.dart @@ -0,0 +1,9 @@ +import 'package:das_client/model/journey/base_data.dart'; +import 'package:das_client/model/journey/service_point.dart'; + +class Metadata { + const Metadata({this.nextStop, this.currentPosition}); + + final ServicePoint? nextStop; + final BaseData? currentPosition; +} diff --git a/das_client/lib/model/journey/service_point.dart b/das_client/lib/model/journey/service_point.dart new file mode 100644 index 00000000..ed829a03 --- /dev/null +++ b/das_client/lib/model/journey/service_point.dart @@ -0,0 +1,22 @@ +import 'package:das_client/model/journey/base_data.dart'; +import 'package:das_client/model/journey/bracket_station.dart'; +import 'package:das_client/model/journey/datatype.dart'; +import 'package:das_client/model/localized_string.dart'; + +class ServicePoint extends BaseData { + ServicePoint( + {required this.name, + required this.mandatoryStop, + required this.isStop, + required this.isStation, + this.bracketStation, + required super.order, + required super.kilometre}) + : super(type: Datatype.servicePoint); + + final LocalizedString name; + final bool mandatoryStop; + final bool isStop; + final bool isStation; + final BracketStation? bracketStation; +} diff --git a/das_client/lib/model/localized_string.dart b/das_client/lib/model/localized_string.dart new file mode 100644 index 00000000..6c6b1b5f --- /dev/null +++ b/das_client/lib/model/localized_string.dart @@ -0,0 +1,26 @@ +import 'dart:io'; + +class LocalizedString { + LocalizedString({ + this.de, + this.fr, + this.it, + }); + + late String? de; + late String? fr; + late String? it; + + String get localized { + final localeName = Platform.localeName; + if (localeName.startsWith('fr') && fr != null) { + return fr!; + } else if (localeName.startsWith('it') && it != null) { + return it!; + } else if (localeName.startsWith('de') && de != null) { + return de!; + } else { + return de ?? fr ?? it ?? ''; + } + } +} diff --git a/das_client/lib/sfera/sfera_component.dart b/das_client/lib/sfera/sfera_component.dart index d1d71cc4..777be722 100644 --- a/das_client/lib/sfera/sfera_component.dart +++ b/das_client/lib/sfera/sfera_component.dart @@ -6,36 +6,7 @@ import 'package:das_client/sfera/src/service/sfera_auth_service.dart'; import 'package:das_client/sfera/src/service/sfera_service.dart'; import 'package:das_client/sfera/src/service/sfera_service_impl.dart'; -export 'package:das_client/sfera/src/model/b2g_request.dart'; -export 'package:das_client/sfera/src/model/das_operating_modes_selected.dart'; -export 'package:das_client/sfera/src/model/das_operating_modes_supported.dart'; -export 'package:das_client/sfera/src/model/g2b_reply_payload.dart'; -export 'package:das_client/sfera/src/model/handshake_acknowledgement.dart'; -export 'package:das_client/sfera/src/model/handshake_reject.dart'; -export 'package:das_client/sfera/src/model/handshake_request.dart'; -export 'package:das_client/sfera/src/model/journey_profile.dart'; -export 'package:das_client/sfera/src/model/jp_request.dart'; -export 'package:das_client/sfera/src/model/message_header.dart'; export 'package:das_client/sfera/src/model/otn_id.dart'; -export 'package:das_client/sfera/src/model/segment_profile.dart'; -export 'package:das_client/sfera/src/model/segment_profile_list.dart'; -export 'package:das_client/sfera/src/model/sfera_b2g_request_message.dart'; -export 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; -export 'package:das_client/sfera/src/model/sfera_xml_element.dart'; -export 'package:das_client/sfera/src/model/signal.dart'; -export 'package:das_client/sfera/src/model/signal_id.dart'; -export 'package:das_client/sfera/src/model/sp_points.dart'; -export 'package:das_client/sfera/src/model/sp_request.dart'; -export 'package:das_client/sfera/src/model/sp_zone.dart'; -export 'package:das_client/sfera/src/model/stopping_point_information.dart'; -export 'package:das_client/sfera/src/model/timing_point.dart'; -export 'package:das_client/sfera/src/model/timing_point_constraints.dart'; -export 'package:das_client/sfera/src/model/timing_point_reference.dart'; -export 'package:das_client/sfera/src/model/tp_id_reference.dart'; -export 'package:das_client/sfera/src/model/tp_name.dart'; -export 'package:das_client/sfera/src/model/train_identification.dart'; -export 'package:das_client/sfera/src/model/virtual_balise.dart'; -export 'package:das_client/sfera/src/model/virtual_balise_position.dart'; export 'package:das_client/sfera/src/repo/sfera_repository.dart'; export 'package:das_client/sfera/src/service/sfera_auth_service.dart'; export 'package:das_client/sfera/src/service/sfera_service.dart'; diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart new file mode 100644 index 00000000..46f82445 --- /dev/null +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -0,0 +1,132 @@ +import 'package:das_client/model/journey/base_data.dart'; +import 'package:das_client/model/journey/bracket_station.dart'; +import 'package:das_client/model/journey/datatype.dart'; +import 'package:das_client/model/journey/journey.dart'; +import 'package:das_client/model/journey/metadata.dart'; +import 'package:das_client/model/journey/service_point.dart'; +import 'package:das_client/model/localized_string.dart'; +import 'package:das_client/sfera/src/model/enums/stop_skip_pass.dart'; +import 'package:das_client/sfera/src/model/enums/taf_tap_location_type.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/segment_profile.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location.dart'; +import 'package:fimber/fimber.dart'; + +class SferaModelMapper { + SferaModelMapper._(); + + static const int _hundredThousand = 100000; + static const String _bracketStationNspName = 'bracketStation'; + static const String _bracketStationMainStationNspName = 'mainStation'; + + static Journey mapToJourney(JourneyProfile journeyProfile, List segmentProfiles) { + try { + return _mapToJourney(journeyProfile, segmentProfiles); + } catch (e, s) { + Fimber.e('Error mapping journey-/segment profiles to journey:', ex: e, stacktrace: s); + return Journey.invalid(); + } + } + + static Journey _mapToJourney(JourneyProfile journeyProfile, List segmentProfiles) { + final journeyData = []; + + final segmentProfilesLists = journeyProfile.segmentProfilesLists.toList(); + + final tafTapLocations = segmentProfiles.expand((it) => it.areas).expand((it) => it.tafTapLocations).toList(); + + 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 kilometreMap = _parseKilometre(segmentProfile); + final timingPoints = segmentProfile.points.expand((it) => it.timingPoints).toList(); + + for (final tpConstraint in segmentProfileList.timingPointsContraints) { + final tpId = tpConstraint.timingPointReference.tpIdReference.tpId; + final timingPoint = timingPoints.where((it) => it.id == tpId).first; + final tafTapLocation = tafTapLocations + .where((it) => + it.locationIdent.countryCodeISO == timingPoint.locationReference?.countryCodeISO && + it.locationIdent.locationPrimaryCode == timingPoint.locationReference?.locationPrimaryCode) + .first; + + journeyData.add(ServicePoint( + name: _localizedStringFromMultilingualText(tafTapLocation.locationNames), + 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] ?? [])); + } + } + + 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), + data: journeyData); + } + + static int _calculateOrder(int segmentIndex, double location) { + return (segmentIndex * _hundredThousand + location).toInt(); + } + + static LocalizedString _localizedStringFromMultilingualText(Iterable multilingualText) { + return LocalizedString( + de: multilingualText.where((it) => it.language == 'de').firstOrNull?.messageString, + fr: multilingualText.where((it) => it.language == 'fr').firstOrNull?.messageString, + it: multilingualText.where((it) => it.language == 'it').firstOrNull?.messageString, + ); + } + + static Map> _parseKilometre(SegmentProfile segmentProfile) { + final kilometreMap = >{}; + for (final point in segmentProfile.contextInformation) { + for (final kilometreReferencePoint in point.kilometreReferencePoints) { + if (!kilometreMap.containsKey(kilometreReferencePoint.location)) { + kilometreMap[kilometreReferencePoint.location] = []; + } + kilometreMap[kilometreReferencePoint.location]!.add(kilometreReferencePoint.kmReference.kmRef); + } + } + return kilometreMap; + } + + static BracketStation? _parseBracketStation(List allLocations, TafTapLocation tafTapLocation) { + for (final tafTapLocationNsp in tafTapLocation.nsp) { + if (tafTapLocationNsp.name == _bracketStationNspName) { + final mainStationNsp = tafTapLocationNsp.networkSpecificParameters + .where((it) => it.name == _bracketStationMainStationNspName) + .firstOrNull; + if (mainStationNsp == null) { + Fimber.w('Encountered bracket station without main station NSP declaration: $tafTapLocation'); + } else { + final countryCode = mainStationNsp.nspValue.substring(0, 2); + final primaryCode = int.parse(mainStationNsp.nspValue.substring(2, 6)); + final mainStation = allLocations + .where((it) => + it.locationIdent.countryCodeISO == countryCode && it.locationIdent.locationPrimaryCode == primaryCode) + .firstOrNull; + if (mainStation == null) { + Fimber.w('Failed to resolve main station for bracket station: $tafTapLocation'); + } else { + return BracketStation( + mainStationAbbreviation: mainStation != tafTapLocation ? mainStation.abbreviation : null); + } + } + } + } + + return null; + } +} diff --git a/das_client/lib/sfera/src/model/enums/stop_skip_pass.dart b/das_client/lib/sfera/src/model/enums/stop_skip_pass.dart new file mode 100644 index 00000000..60eda705 --- /dev/null +++ b/das_client/lib/sfera/src/model/enums/stop_skip_pass.dart @@ -0,0 +1,14 @@ +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; + +enum StopSkipPass implements XmlEnum { + stoppingPoint(xmlValue: 'Stopping_Point'), + skippedStoppingPoint(xmlValue: 'Skipped_Stopping_Point'), + passingPoint(xmlValue: 'Passing_Point'); + + const StopSkipPass({ + required this.xmlValue, + }); + + @override + final String xmlValue; +} diff --git a/das_client/lib/sfera/src/model/enums/taf_tap_location_type.dart b/das_client/lib/sfera/src/model/enums/taf_tap_location_type.dart new file mode 100644 index 00000000..da2e3be0 --- /dev/null +++ b/das_client/lib/sfera/src/model/enums/taf_tap_location_type.dart @@ -0,0 +1,13 @@ +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; + +enum TafTapLocationType implements XmlEnum { + station(xmlValue: 'station'), + stoppingLocation(xmlValue: 'stopping location'); + + const TafTapLocationType({ + required this.xmlValue, + }); + + @override + final String xmlValue; +} diff --git a/das_client/lib/sfera/src/model/enums/xml_enum.dart b/das_client/lib/sfera/src/model/enums/xml_enum.dart index 6b59ad07..77b94016 100644 --- a/das_client/lib/sfera/src/model/enums/xml_enum.dart +++ b/das_client/lib/sfera/src/model/enums/xml_enum.dart @@ -3,6 +3,6 @@ abstract interface class XmlEnum { String get xmlValue; static T? valueOf(List allValues, String? xmlValue) { - return allValues.where((it) => it.xmlValue == xmlValue).firstOrNull; + return allValues.where((it) => it.xmlValue.toLowerCase() == xmlValue?.toLowerCase()).firstOrNull; } } diff --git a/das_client/lib/sfera/src/model/handshake_acknowledgement.dart b/das_client/lib/sfera/src/model/handshake_acknowledgement.dart index a4708357..5fc87d70 100644 --- a/das_client/lib/sfera/src/model/handshake_acknowledgement.dart +++ b/das_client/lib/sfera/src/model/handshake_acknowledgement.dart @@ -10,6 +10,6 @@ class HandshakeAcknowledgement extends SferaXmlElement { @override bool validate() { - return validateHasChild('DAS_OperatingModeSelected') && super.validate(); + return validateHasChildOfType() && super.validate(); } } diff --git a/das_client/lib/sfera/src/model/journey_profile.dart b/das_client/lib/sfera/src/model/journey_profile.dart index 0cd8dc85..b363682a 100644 --- a/das_client/lib/sfera/src/model/journey_profile.dart +++ b/das_client/lib/sfera/src/model/journey_profile.dart @@ -17,6 +17,6 @@ class JourneyProfile extends SferaXmlElement { @override bool validate() { - return validateHasChild('TrainIdentification') && super.validate(); + return validateHasChildOfType() && super.validate(); } } diff --git a/das_client/lib/sfera/src/model/kilometre_reference_point.dart b/das_client/lib/sfera/src/model/kilometre_reference_point.dart new file mode 100644 index 00000000..a0de91bf --- /dev/null +++ b/das_client/lib/sfera/src/model/kilometre_reference_point.dart @@ -0,0 +1,15 @@ +import 'package:das_client/sfera/src/model/km_reference.dart'; +import 'package:das_client/sfera/src/model/sp_generic_point.dart'; + +class KilometreReferencePoint extends SpGenericPoint { + static const String elementType = 'KilometreReferencePoint'; + + KilometreReferencePoint({super.type = elementType, super.attributes, super.children, super.value}); + + KmReference get kmReference => children.whereType().first; + + @override + bool validate() { + return validateHasChildOfType() && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/km_reference.dart b/das_client/lib/sfera/src/model/km_reference.dart new file mode 100644 index 00000000..5f670798 --- /dev/null +++ b/das_client/lib/sfera/src/model/km_reference.dart @@ -0,0 +1,14 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class KmReference extends SferaXmlElement { + static const String elementType = 'KM_Reference'; + + KmReference({super.type = elementType, super.attributes, super.children, super.value}); + + double get kmRef => double.parse(attributes['kmRef']!); + + @override + bool validate() { + return validateHasAttributeDouble('kmRef') && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/location_ident.dart b/das_client/lib/sfera/src/model/location_ident.dart new file mode 100644 index 00000000..e8aae6ab --- /dev/null +++ b/das_client/lib/sfera/src/model/location_ident.dart @@ -0,0 +1,16 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class LocationIdent extends SferaXmlElement { + static const String elementType = 'LocationIdent'; + + LocationIdent({super.type = elementType, super.attributes, super.children, super.value}); + + String get countryCodeISO => childrenWithType('CountryCodeISO').first.value!; + + int get locationPrimaryCode => int.parse(childrenWithType('LocationPrimaryCode').first.value!); + + @override + bool validate() { + return validateHasChild('CountryCodeISO') && validateHasChildInt('LocationPrimaryCode') && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/multilingual_text.dart b/das_client/lib/sfera/src/model/multilingual_text.dart new file mode 100644 index 00000000..54c99caa --- /dev/null +++ b/das_client/lib/sfera/src/model/multilingual_text.dart @@ -0,0 +1,16 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class MultilingualText extends SferaXmlElement { + static const String elementType = 'MultilingualText'; + + MultilingualText({super.type = elementType, super.attributes, super.children, super.value}); + + String get language => attributes['language']!; + + String get messageString => attributes['messageString']!; + + @override + bool validate() { + return validateHasAttribute('language') && validateHasAttribute('messageString') && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/network_specific_parameter.dart b/das_client/lib/sfera/src/model/network_specific_parameter.dart new file mode 100644 index 00000000..aa1b3f8d --- /dev/null +++ b/das_client/lib/sfera/src/model/network_specific_parameter.dart @@ -0,0 +1,16 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class NetworkSpecificParameter extends SferaXmlElement { + static const String elementType = 'NetworkSpecificParameter'; + + NetworkSpecificParameter({super.type = elementType, super.attributes, super.children, super.value}); + + String get name => attributes['name']!; + + String get nspValue => attributes['value']!; + + @override + bool validate() { + return validateHasAttribute('name') && validateHasAttribute('value') && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/nsp.dart b/das_client/lib/sfera/src/model/nsp.dart new file mode 100644 index 00000000..a0e1b41e --- /dev/null +++ b/das_client/lib/sfera/src/model/nsp.dart @@ -0,0 +1,17 @@ +import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +abstract class Nsp extends SferaXmlElement { + static const String elementType = 'NSP'; + + Nsp({super.type = elementType, super.attributes, super.children, super.value}); + + String get name => attributes['name']!; + + Iterable get networkSpecificParameters => children.whereType(); + + @override + bool validate() { + return validateHasAttribute('name') && validateHasChildOfType() && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/segment_profile.dart b/das_client/lib/sfera/src/model/segment_profile.dart index 60714fdf..c1706fcd 100644 --- a/das_client/lib/sfera/src/model/segment_profile.dart +++ b/das_client/lib/sfera/src/model/segment_profile.dart @@ -1,6 +1,8 @@ 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/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/sp_areas.dart'; +import 'package:das_client/sfera/src/model/sp_context_information.dart'; import 'package:das_client/sfera/src/model/sp_points.dart'; import 'package:das_client/sfera/src/model/sp_zone.dart'; @@ -21,7 +23,11 @@ class SegmentProfile extends SferaXmlElement { SpZone? get zone => children.whereType().firstOrNull; - SpPoints? get points => children.whereType().firstOrNull; + Iterable get points => children.whereType(); + + Iterable get contextInformation => children.whereType(); + + Iterable get areas => children.whereType(); @override bool validate() { diff --git a/das_client/lib/sfera/src/model/segment_profile_list.dart b/das_client/lib/sfera/src/model/segment_profile_list.dart index 9152756a..c34ca5dd 100644 --- a/das_client/lib/sfera/src/model/segment_profile_list.dart +++ b/das_client/lib/sfera/src/model/segment_profile_list.dart @@ -15,7 +15,7 @@ class SegmentProfileList extends SferaXmlElement { SpZone get spZone => children.whereType().first; - Iterable get timingPoints => children.whereType(); + Iterable get timingPointsContraints => children.whereType(); @override bool validate() { diff --git a/das_client/lib/sfera/src/model/sfera_xml_element.dart b/das_client/lib/sfera/src/model/sfera_xml_element.dart index 675f315f..a6c794c3 100644 --- a/das_client/lib/sfera/src/model/sfera_xml_element.dart +++ b/das_client/lib/sfera/src/model/sfera_xml_element.dart @@ -1,5 +1,6 @@ import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; import 'package:fimber/fimber.dart'; +import 'package:flutter/material.dart'; import 'package:xml/xml.dart'; class SferaXmlElement { @@ -12,6 +13,7 @@ class SferaXmlElement { : attributes = attributes ?? {}, children = children ?? []; + @mustCallSuper bool validate() { return children.every((it) => it.validate()); } @@ -25,6 +27,21 @@ class SferaXmlElement { return true; } + bool validateHasAttributeDouble(String attribute) { + if (!attributes.containsKey(attribute)) { + Fimber.w('Validation failed for $type because attribute=$attribute is missing'); + return false; + } + + if (double.tryParse(attributes[attribute]!) == null) { + Fimber.w( + 'Validation failed for $type because attribute=$attribute with value=${attributes[attribute]} could not be parsed to double'); + return false; + } + + return true; + } + bool validateHasChild(String type) { if (childrenWithType(type).isEmpty) { Fimber.w('Validation failed for ${this.type} because it has no child of type $type'); @@ -34,6 +51,22 @@ class SferaXmlElement { return true; } + bool validateHasChildInt(String type) { + if (!validateHasChild(type)) { + return false; + } + + final childValue = childrenWithType(type).first.value; + + if (childValue == null || int.tryParse(childValue) == null) { + Fimber.w( + 'Validation failed for ${this.type} because child of type=$type with value=$childValue could not be parsed to int'); + return false; + } + + return true; + } + bool validateHasChildOfType() { if (children.whereType().isEmpty) { Fimber.w('Validation failed for $type because it has no child of type ${T.toString()}'); @@ -88,4 +121,9 @@ class SferaXmlElement { } }); } + + @override + String toString() { + return buildDocument().toString(); + } } diff --git a/das_client/lib/sfera/src/model/signal.dart b/das_client/lib/sfera/src/model/signal.dart index d0eaa9ee..ef335c70 100644 --- a/das_client/lib/sfera/src/model/signal.dart +++ b/das_client/lib/sfera/src/model/signal.dart @@ -10,6 +10,6 @@ class Signal extends SferaXmlElement { @override bool validate() { - return validateHasChild('Signal_ID') && super.validate(); + return validateHasChildOfType() && super.validate(); } } diff --git a/das_client/lib/sfera/src/model/sp_areas.dart b/das_client/lib/sfera/src/model/sp_areas.dart new file mode 100644 index 00000000..a3b1b89e --- /dev/null +++ b/das_client/lib/sfera/src/model/sp_areas.dart @@ -0,0 +1,10 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location.dart'; + +class SpAreas extends SferaXmlElement { + static const String elementType = 'SP_Areas'; + + SpAreas({super.type = elementType, super.attributes, super.children, super.value}); + + Iterable get tafTapLocations => children.whereType(); +} diff --git a/das_client/lib/sfera/src/model/sp_context_information.dart b/das_client/lib/sfera/src/model/sp_context_information.dart new file mode 100644 index 00000000..3a63a9cd --- /dev/null +++ b/das_client/lib/sfera/src/model/sp_context_information.dart @@ -0,0 +1,10 @@ +import 'package:das_client/sfera/src/model/kilometre_reference_point.dart'; +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class SpContextInformation extends SferaXmlElement { + static const String elementType = 'SP_ContextInformation'; + + SpContextInformation({super.type = elementType, super.attributes, super.children, super.value}); + + Iterable get kilometreReferencePoints => children.whereType(); +} diff --git a/das_client/lib/sfera/src/model/sp_generic_point.dart b/das_client/lib/sfera/src/model/sp_generic_point.dart new file mode 100644 index 00000000..dbd92169 --- /dev/null +++ b/das_client/lib/sfera/src/model/sp_generic_point.dart @@ -0,0 +1,12 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +abstract class SpGenericPoint extends SferaXmlElement { + SpGenericPoint({required super.type, super.attributes, super.children, super.value}); + + double get location => double.parse(attributes['location']!); + + @override + bool validate() { + return validateHasAttributeDouble('location') && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/sp_zone.dart b/das_client/lib/sfera/src/model/sp_zone.dart index 70dd8298..cfde7a4a 100644 --- a/das_client/lib/sfera/src/model/sp_zone.dart +++ b/das_client/lib/sfera/src/model/sp_zone.dart @@ -11,6 +11,6 @@ class SpZone extends SferaXmlElement { @override bool validate() { - return validateHasChild('IM_ID') || validateHasChild('NID_C'); + return (validateHasChild('IM_ID') || validateHasChild('NID_C')) && super.validate(); } } diff --git a/das_client/lib/sfera/src/model/stop_type.dart b/das_client/lib/sfera/src/model/stop_type.dart new file mode 100644 index 00000000..4f7f9a95 --- /dev/null +++ b/das_client/lib/sfera/src/model/stop_type.dart @@ -0,0 +1,20 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class StopType extends SferaXmlElement { + static const String elementType = 'StopType'; + + StopType({super.type = elementType, super.attributes, super.children, super.value}); + + String get stopTypePurpose => attributes['stopTypePurpose']!; + + String? get stopTypeDetails => attributes['stopTypeDetails']; + + bool? get plannedStop => bool.tryParse(attributes['plannedStop'] ?? ''); + + bool? get mandatoryStop => bool.tryParse(attributes['mandatoryStop'] ?? ''); + + @override + bool validate() { + return validateHasAttribute('stopTypePurpose') && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/stopping_point_information.dart b/das_client/lib/sfera/src/model/stopping_point_information.dart index 8e671d77..735e003d 100644 --- a/das_client/lib/sfera/src/model/stopping_point_information.dart +++ b/das_client/lib/sfera/src/model/stopping_point_information.dart @@ -1,11 +1,14 @@ import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/stop_type.dart'; class StoppingPointInformation extends SferaXmlElement { static const String elementType = 'StoppingPointInformation'; StoppingPointInformation({super.type = elementType, super.attributes, super.children, super.value}); - String get departureTime => attributes['departureTime']!; + DateTime get departureTime => DateTime.parse(attributes['departureTime']!); + + StopType? get stopType => children.whereType().firstOrNull; @override bool validate() { diff --git a/das_client/lib/sfera/src/model/taf_tap_location.dart b/das_client/lib/sfera/src/model/taf_tap_location.dart new file mode 100644 index 00000000..576c3570 --- /dev/null +++ b/das_client/lib/sfera/src/model/taf_tap_location.dart @@ -0,0 +1,28 @@ +import 'package:das_client/sfera/src/model/enums/taf_tap_location_type.dart'; +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location_ident.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location_name.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location_nsp.dart'; + +class TafTapLocation extends SferaXmlElement { + static const String elementType = 'TAF_TAP_Location'; + + TafTapLocation({super.type = elementType, super.attributes, super.children, super.value}); + + TafTapLocationIdent get locationIdent => children.whereType().first; + + Iterable get locationNames => children.whereType(); + + TafTapLocationType? get locationType => + XmlEnum.valueOf(TafTapLocationType.values, attributes['TAF_TAP_location_type']); + + String get abbreviation => attributes['TAF_TAP_location_abbreviation'] ?? ''; + + Iterable get nsp => children.whereType(); + + @override + bool validate() { + return validateHasChildOfType() && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/taf_tap_location_ident.dart b/das_client/lib/sfera/src/model/taf_tap_location_ident.dart new file mode 100644 index 00000000..00fe550a --- /dev/null +++ b/das_client/lib/sfera/src/model/taf_tap_location_ident.dart @@ -0,0 +1,7 @@ +import 'package:das_client/sfera/src/model/location_ident.dart'; + +class TafTapLocationIdent extends LocationIdent { + static const String elementType = 'TAF_TAP_LocationIdent'; + + TafTapLocationIdent({super.type = elementType, super.attributes, super.children, super.value}); +} diff --git a/das_client/lib/sfera/src/model/taf_tap_location_name.dart b/das_client/lib/sfera/src/model/taf_tap_location_name.dart new file mode 100644 index 00000000..3775ec45 --- /dev/null +++ b/das_client/lib/sfera/src/model/taf_tap_location_name.dart @@ -0,0 +1,7 @@ +import 'package:das_client/sfera/src/model/multilingual_text.dart'; + +class TafTapLocationName extends MultilingualText { + static const String elementType = 'TAF_TAP_LocationName'; + + TafTapLocationName({super.type = elementType, super.attributes, super.children, super.value}); +} diff --git a/das_client/lib/sfera/src/model/taf_tap_location_nsp.dart b/das_client/lib/sfera/src/model/taf_tap_location_nsp.dart new file mode 100644 index 00000000..0063b020 --- /dev/null +++ b/das_client/lib/sfera/src/model/taf_tap_location_nsp.dart @@ -0,0 +1,7 @@ +import 'package:das_client/sfera/src/model/nsp.dart'; + +class TafTapLocationNsp extends Nsp { + static const String elementType = 'TAF_TAP_Location_NSP'; + + TafTapLocationNsp({super.type = elementType, super.attributes, super.children, super.value}); +} diff --git a/das_client/lib/sfera/src/model/taf_tap_location_reference.dart b/das_client/lib/sfera/src/model/taf_tap_location_reference.dart new file mode 100644 index 00000000..0ff4efef --- /dev/null +++ b/das_client/lib/sfera/src/model/taf_tap_location_reference.dart @@ -0,0 +1,16 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class TafTapLocationReference extends SferaXmlElement { + static const String elementType = 'TAF_TAP_LocationReference'; + + TafTapLocationReference({super.type = elementType, super.attributes, super.children, super.value}); + + String get countryCodeISO => childrenWithType('CountryCodeISO').first.value!; + + int get locationPrimaryCode => int.parse(childrenWithType('LocationPrimaryCode').first.value!); + + @override + bool validate() { + return validateHasChild('CountryCodeISO') && validateHasChildInt('LocationPrimaryCode') && super.validate(); + } +} diff --git a/das_client/lib/sfera/src/model/timing_point.dart b/das_client/lib/sfera/src/model/timing_point.dart index 4d63e09b..f569ecc2 100644 --- a/das_client/lib/sfera/src/model/timing_point.dart +++ b/das_client/lib/sfera/src/model/timing_point.dart @@ -1,4 +1,5 @@ import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location_reference.dart'; import 'package:das_client/sfera/src/model/tp_name.dart'; class TimingPoint extends SferaXmlElement { @@ -8,12 +9,14 @@ class TimingPoint extends SferaXmlElement { String get id => attributes['TP_ID']!; - String get location => attributes['location']!; + double get location => double.parse(attributes['location']!); Iterable get names => children.whereType(); + TafTapLocationReference? get locationReference => children.whereType().firstOrNull; + @override bool validate() { - return validateHasAttribute('TP_ID') && validateHasAttribute('location') && super.validate(); + return validateHasAttribute('TP_ID') && validateHasAttributeDouble('location') && super.validate(); } } diff --git a/das_client/lib/sfera/src/model/timing_point_constraints.dart b/das_client/lib/sfera/src/model/timing_point_constraints.dart index db99abe6..c4fae387 100644 --- a/das_client/lib/sfera/src/model/timing_point_constraints.dart +++ b/das_client/lib/sfera/src/model/timing_point_constraints.dart @@ -1,4 +1,7 @@ +import 'package:das_client/sfera/src/model/enums/stop_skip_pass.dart'; +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/stopping_point_information.dart'; import 'package:das_client/sfera/src/model/timing_point_reference.dart'; class TimingPointConstraints extends SferaXmlElement { @@ -8,6 +11,11 @@ class TimingPointConstraints extends SferaXmlElement { TimingPointReference get timingPointReference => children.whereType().first; + StoppingPointInformation? get stoppingPointInformation => children.whereType().firstOrNull; + + StopSkipPass get stopSkipPass => + XmlEnum.valueOf(StopSkipPass.values, attributes['TP_StopSkipPass']) ?? StopSkipPass.stoppingPoint; + @override bool validate() { return validateHasChildOfType() && super.validate(); diff --git a/das_client/lib/sfera/src/model/timing_point_reference.dart b/das_client/lib/sfera/src/model/timing_point_reference.dart index db670a2c..0ecc8160 100644 --- a/das_client/lib/sfera/src/model/timing_point_reference.dart +++ b/das_client/lib/sfera/src/model/timing_point_reference.dart @@ -1,7 +1,16 @@ + import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; +import 'package:das_client/sfera/src/model/tp_id_reference.dart'; class TimingPointReference extends SferaXmlElement { static const String elementType = 'TimingPointReference'; + TpIdReference get tpIdReference => children.whereType().first; + TimingPointReference({super.type = elementType, super.attributes, super.children, super.value}); + + @override + bool validate() { + return validateHasChildOfType() && super.validate(); + } } diff --git a/das_client/lib/sfera/src/repo/sfera_repository_impl.dart b/das_client/lib/sfera/src/repo/sfera_repository_impl.dart index 2a1aa298..e65e5fd4 100644 --- a/das_client/lib/sfera/src/repo/sfera_repository_impl.dart +++ b/das_client/lib/sfera/src/repo/sfera_repository_impl.dart @@ -1,6 +1,8 @@ import 'package:das_client/sfera/sfera_component.dart'; import 'package:das_client/sfera/src/db/journey_profile_entity.dart'; import 'package:das_client/sfera/src/db/segment_profile_entity.dart'; +import 'package:das_client/sfera/src/model/journey_profile.dart'; +import 'package:das_client/sfera/src/model/segment_profile.dart'; import 'package:fimber/fimber.dart'; import 'package:isar/isar.dart'; import 'package:path_provider/path_provider.dart'; diff --git a/das_client/lib/sfera/src/service/sfera_service.dart b/das_client/lib/sfera/src/service/sfera_service.dart index e7f264fd..9741db50 100644 --- a/das_client/lib/sfera/src/service/sfera_service.dart +++ b/das_client/lib/sfera/src/service/sfera_service.dart @@ -1,6 +1,9 @@ import 'dart:core'; +import 'package:das_client/model/journey/journey.dart'; import 'package:das_client/sfera/sfera_component.dart'; +import 'package:das_client/sfera/src/model/message_header.dart'; +import 'package:das_client/sfera/src/model/train_identification.dart'; import 'package:das_client/util/device_id_info.dart'; import 'package:das_client/util/error_code.dart'; import 'package:das_client/util/format.dart'; @@ -11,9 +14,7 @@ abstract class SferaService { Stream get stateStream; - Stream get journeyStream; - - Stream> get segmentStream; + Stream get journeyStream; ErrorCode? get lastErrorCode; 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 bf337a6a..e7682c46 100644 --- a/das_client/lib/sfera/src/service/sfera_service_impl.dart +++ b/das_client/lib/sfera/src/service/sfera_service_impl.dart @@ -2,9 +2,14 @@ import 'dart:async'; import 'dart:core'; import 'package:das_client/auth/authentication_component.dart'; +import 'package:das_client/model/journey/journey.dart'; import 'package:das_client/mqtt/mqtt_component.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/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/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'; @@ -28,18 +33,14 @@ class SferaServiceImpl implements SferaService { @override Stream get stateStream => _stateSubject.stream; - - final _journeyProfileSubject = BehaviorSubject.seeded(null); - - @override - Stream get journeyStream => _journeyProfileSubject.stream; - - final _segmentProfilesSubject = BehaviorSubject>(); + final _journeyProfileSubject = BehaviorSubject.seeded(null); @override - Stream> get segmentStream => _segmentProfilesSubject.stream; + Stream get journeyStream => _journeyProfileSubject.stream; - OtnId? otnId; + OtnId? _otnId; + JourneyProfile? _journeyProfile; + final List _segmentProfiles = []; @override ErrorCode? lastErrorCode; @@ -75,7 +76,7 @@ class SferaServiceImpl implements SferaService { @override Future connect(OtnId otnId) async { Fimber.i('Starting new connection for $otnId'); - this.otnId = otnId; + _otnId = otnId; _messageHandlers.clear(); lastErrorCode = null; _stateSubject.add(SferaServiceState.connecting); @@ -90,7 +91,7 @@ class SferaServiceImpl implements SferaService { _messageHandlers.add(handshakeTask); handshakeTask.execute(onTaskCompleted, onTaskFailed); } else { - this.otnId = null; + _otnId = null; lastErrorCode = ErrorCode.connectionFailed; _stateSubject.add(SferaServiceState.disconnected); } @@ -102,37 +103,50 @@ class SferaServiceImpl implements SferaService { if (task is HandshakeTask) { _stateSubject.add(SferaServiceState.loadingJourney); final requestJourneyTask = - RequestJourneyProfileTask(mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: otnId!); + RequestJourneyProfileTask(mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: _otnId!); _messageHandlers.add(requestJourneyTask); requestJourneyTask.execute(onTaskCompleted, onTaskFailed); } else if (task is RequestJourneyProfileTask) { _stateSubject.add(SferaServiceState.loadingSegments); final requestSegmentProfilesTask = RequestSegmentProfilesTask( - mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: otnId!, journeyProfile: data); - _journeyProfileSubject.add(data); + mqttService: _mqttService, sferaRepository: _sferaRepository, otnId: _otnId!, journeyProfile: data); + _journeyProfile = data; _messageHandlers.add(requestSegmentProfilesTask); requestSegmentProfilesTask.execute(onTaskCompleted, onTaskFailed); } else if (task is RequestSegmentProfilesTask) { _addMessageHandlers(); await _refreshSegmentProfiles(); + _updateJourney(); _stateSubject.add(SferaServiceState.connected); } } Future _refreshSegmentProfiles() async { - if (_journeyProfileSubject.value != null) { - final segments = []; + if (_journeyProfile != null) { + _segmentProfiles.clear(); - for (final element in _journeyProfileSubject.value!.segmentProfilesLists) { + for (final element in _journeyProfile!.segmentProfilesLists) { final segmentProfileEntity = await _sferaRepository.findSegmentProfile(element.spId, element.versionMajor, element.versionMinor); if (segmentProfileEntity != null) { - segments.add(segmentProfileEntity.toDomain()); + _segmentProfiles.add(segmentProfileEntity.toDomain()); } else { Fimber.w('Could not find segment profile for ${element.spId}'); } } - _segmentProfilesSubject.add(segments); + } + } + + void _updateJourney() { + if (_journeyProfile != null && _segmentProfiles.isNotEmpty) { + Fimber.i('Updating journey stream...'); + final newJourney = SferaModelMapper.mapToJourney(_journeyProfile!, _segmentProfiles); + if (newJourney.valid) { + _journeyProfileSubject.add(SferaModelMapper.mapToJourney(_journeyProfile!, _segmentProfiles)); + Fimber.i('Journey updates successfully.'); + } else { + Fimber.w('Failed to update journey as it is not valid'); + } } } diff --git a/das_client/lib/sfera/src/sfera_reply_parser.dart b/das_client/lib/sfera/src/sfera_reply_parser.dart index 97076363..1339a320 100644 --- a/das_client/lib/sfera/src/sfera_reply_parser.dart +++ b/das_client/lib/sfera/src/sfera_reply_parser.dart @@ -3,7 +3,12 @@ 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'; import 'package:das_client/sfera/src/model/journey_profile.dart'; +import 'package:das_client/sfera/src/model/kilometre_reference_point.dart'; +import 'package:das_client/sfera/src/model/km_reference.dart'; +import 'package:das_client/sfera/src/model/location_ident.dart'; import 'package:das_client/sfera/src/model/message_header.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/otn_id.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; import 'package:das_client/sfera/src/model/segment_profile_list.dart'; @@ -11,9 +16,17 @@ 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'; import 'package:das_client/sfera/src/model/signal_id.dart'; +import 'package:das_client/sfera/src/model/sp_areas.dart'; +import 'package:das_client/sfera/src/model/sp_context_information.dart'; import 'package:das_client/sfera/src/model/sp_points.dart'; import 'package:das_client/sfera/src/model/sp_zone.dart'; +import 'package:das_client/sfera/src/model/stop_type.dart'; import 'package:das_client/sfera/src/model/stopping_point_information.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location_ident.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location_name.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location_nsp.dart'; +import 'package:das_client/sfera/src/model/taf_tap_location_reference.dart'; import 'package:das_client/sfera/src/model/timing_point.dart'; import 'package:das_client/sfera/src/model/timing_point_constraints.dart'; import 'package:das_client/sfera/src/model/timing_point_reference.dart'; @@ -101,6 +114,32 @@ class SferaReplyParser { return HandshakeReject(type: type, attributes: attributes, children: children, value: value); case DasOperatingModesSelected.elementType: return DasOperatingModesSelected(type: type, attributes: attributes, children: children, value: value); + case SpContextInformation.elementType: + return SpContextInformation(type: type, attributes: attributes, children: children, value: value); + case KilometreReferencePoint.elementType: + return KilometreReferencePoint(type: type, attributes: attributes, children: children, value: value); + case KmReference.elementType: + return KmReference(type: type, attributes: attributes, children: children, value: value); + case SpAreas.elementType: + return SpAreas(type: type, attributes: attributes, children: children, value: value); + case TafTapLocation.elementType: + return TafTapLocation(type: type, attributes: attributes, children: children, value: value); + case LocationIdent.elementType: + return LocationIdent(type: type, attributes: attributes, children: children, value: value); + case TafTapLocationIdent.elementType: + return TafTapLocationIdent(type: type, attributes: attributes, children: children, value: value); + case MultilingualText.elementType: + return MultilingualText(type: type, attributes: attributes, children: children, value: value); + case TafTapLocationName.elementType: + return TafTapLocationName(type: type, attributes: attributes, children: children, value: value); + case TafTapLocationReference.elementType: + return TafTapLocationReference(type: type, attributes: attributes, children: children, value: value); + case StopType.elementType: + return StopType(type: type, attributes: attributes, children: children, value: value); + 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); default: return SferaXmlElement(type: type, attributes: attributes, children: children, value: value); } diff --git a/das_client/pubspec.lock b/das_client/pubspec.lock index 55493976..c764109f 100644 --- a/das_client/pubspec.lock +++ b/das_client/pubspec.lock @@ -469,6 +469,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "578bd8c508144fdaffd4f77b8ef2d8c523602275cd697cc3db284dbd762ef4ce" + url: "https://pub.dev" + source: hosted + version: "2.0.14" flutter_test: dependency: "direct dev" description: flutter @@ -769,6 +777,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider: dependency: "direct main" description: @@ -1074,6 +1090,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "773c9522d66d523e1c7b25dfb95cc91c26a1e17b107039cfe147285e92de7878" + url: "https://pub.dev" + source: hosted + version: "1.1.14" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" + url: "https://pub.dev" + source: hosted + version: "1.1.12" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: ab9ff38fc771e9ee1139320adbe3d18a60327370c218c60752068ebee4b49ab1 + url: "https://pub.dev" + source: hosted + version: "1.1.15" vector_math: dependency: transitive description: diff --git a/das_client/pubspec.yaml b/das_client/pubspec.yaml index b5315470..8b12709b 100644 --- a/das_client/pubspec.yaml +++ b/das_client/pubspec.yaml @@ -1,32 +1,11 @@ name: das_client -description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. +description: "DAS (Driver Advisory System) is a mobile application that provides all the required journey data to the train driver." publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. version: 0.2.1+37 environment: sdk: '>=3.3.0 <4.0.0' -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter @@ -82,6 +61,8 @@ dependencies: package_info_plus: ^8.0.3 # https://pub.dev/packages/synchronized synchronized: ^3.3.0 + # https://pub.dev/packages/flutter_svg + flutter_svg: ^2.0.14 dev_dependencies: integration_test: @@ -95,11 +76,6 @@ dev_dependencies: path_provider_platform_interface: any plugin_platform_interface: any - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^5.0.0 custom_lint: das_custom_lints: @@ -107,45 +83,9 @@ dev_dependencies: flutter_launcher_icons: ^0.14.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - generate: true - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + assets: + - assets/ + - assets/icons/ diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart new file mode 100644 index 00000000..b1d9e735 --- /dev/null +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -0,0 +1,137 @@ +import 'dart:io'; + +import 'package:das_client/model/journey/datatype.dart'; +import 'package:das_client/model/journey/journey.dart'; +import 'package:das_client/model/journey/service_point.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/journey_profile.dart'; +import 'package:das_client/sfera/src/model/segment_profile.dart'; +import 'package:fimber/fimber.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + Fimber.plantTree(DebugTree()); + + List getFilesForSp(String baseName, int count) { + final files = []; + for (var i = 1; i <= count; i++) { + files.add(File('test_resources/sp/${baseName}_$i.xml')); + } + return files; + } + + Journey getJourney(String trainNumber, int spCount) { + final journeyFile = File('test_resources/jp/SFERA_JP_$trainNumber.xml'); + final journeyProfile = SferaReplyParser.parse(journeyFile.readAsStringSync()); + expect(journeyProfile.validate(), true); + final List segmentProfiles = []; + + for (final File file in getFilesForSp('SFERA_SP_$trainNumber', spCount)) { + final segmentProfile = SferaReplyParser.parse(file.readAsStringSync()); + expect(segmentProfile.validate(), true); + segmentProfiles.add(segmentProfile); + } + + return SferaModelMapper.mapToJourney(journeyProfile, segmentProfiles); + } + + test('Test invalid journey on SP missing', () async { + final journey = getJourney('9999', 4); + + expect(journey.valid, false); + }); + + test('Test service point names are resolved correctly', () async { + final journey = getJourney('9999', 5); + + final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); + + expect(journey.valid, true); + expect(servicePoints, hasLength(5)); + 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'); + }); + + test('Test kilometre are parsed correctly', () async { + final journey = getJourney('9999', 5); + + expect(journey.valid, true); + expect(journey.data, hasLength(5)); + expect(journey.data[0].kilometre[0], 0.5); + expect(journey.data[1].kilometre[0], 1.5); + expect(journey.data[2].kilometre[0], 2.4); + expect(journey.data[3].kilometre[0], 3.7); + expect(journey.data[3].kilometre[1], 0); + expect(journey.data[4].kilometre[0], 0.6); + }); + + test('Test order is generated correctly', () async { + final journey = getJourney('9999', 5); + + expect(journey.valid, true); + expect(journey.data, hasLength(5)); + expect(journey.data[0].order, 000500); + expect(journey.data[1].order, 100500); + expect(journey.data[2].order, 200400); + expect(journey.data[3].order, 300700); + expect(journey.data[4].order, 400300); + }); + + test('Test stop on demand is parsed correctly', () async { + final journey = getJourney('9999', 5); + final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); + + expect(journey.valid, true); + expect(servicePoints, hasLength(5)); + 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); + }); + + test('Test passing point is parsed correctly', () async { + final journey = getJourney('9999', 5); + final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); + + expect(journey.valid, true); + expect(servicePoints, hasLength(5)); + 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); + }); + + test('Test station point is parsed correctly', () async { + final journey = getJourney('9999', 5); + final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); + + expect(journey.valid, true); + expect(servicePoints, hasLength(5)); + 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); + }); + + test('Test bracket stations is parsed correctly', () async { + final journey = getJourney('9999', 5); + final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); + + expect(journey.valid, true); + expect(servicePoints, hasLength(5)); + expect(servicePoints[0].bracketStation, isNull); + expect(servicePoints[1].bracketStation, isNull); + expect(servicePoints[2].bracketStation, isNull); + expect(servicePoints[3].bracketStation, isNotNull); + expect(servicePoints[3].bracketStation!.mainStationAbbreviation, isNull); + expect(servicePoints[4].bracketStation, isNotNull); + expect(servicePoints[4].bracketStation!.mainStationAbbreviation, 'D'); + }); +} diff --git a/das_client/test/sfera/model/sfera_document_test.dart b/das_client/test/sfera/model/sfera_document_test.dart index ad6dbdf4..e44ffa43 100644 --- a/das_client/test/sfera/model/sfera_document_test.dart +++ b/das_client/test/sfera/model/sfera_document_test.dart @@ -56,19 +56,21 @@ void main() { expect(payload.segmentProfiles.first.zone, isNotNull); expect(payload.segmentProfiles.first.zone!.imId, '0088'); - expect(payload.segmentProfiles.first.points!.timingPoints, hasLength(3)); - expect(payload.segmentProfiles.first.points!.timingPoints.first.id, '1837'); - expect(payload.segmentProfiles.first.points!.timingPoints.first.location, '0'); - expect(payload.segmentProfiles.first.points!.timingPoints.first.names.first.name, 'MEER-GRENS'); - - expect(payload.segmentProfiles.first.points!.signals, hasLength(9)); - expect(payload.segmentProfiles.first.points!.signals.first.id.physicalId, '102346'); - expect(payload.segmentProfiles.first.points!.signals.first.id.location, '843'); - - expect(payload.segmentProfiles.first.points!.balise, hasLength(3)); - expect(payload.segmentProfiles.first.points!.balise.first.location, '0'); - expect(payload.segmentProfiles.first.points!.balise.first.position.latitude, '51.48591'); - expect(payload.segmentProfiles.first.points!.balise.first.position.longitude, '4.73459'); + final spPoint = payload.segmentProfiles.first.points.first; + + expect(spPoint.timingPoints, hasLength(3)); + expect(spPoint.timingPoints.first.id, '1837'); + expect(spPoint.timingPoints.first.location, 0); + expect(spPoint.timingPoints.first.names.first.name, 'MEER-GRENS'); + + expect(spPoint.signals, hasLength(9)); + expect(spPoint.signals.first.id.physicalId, '102346'); + expect(spPoint.signals.first.id.location, '843'); + + expect(spPoint.balise, hasLength(3)); + expect(spPoint.balise.first.location, '0'); + expect(spPoint.balise.first.position.latitude, '51.48591'); + expect(spPoint.balise.first.position.longitude, '4.73459'); }); test('Test Sfera HandshakeRequest generation', () async { diff --git a/das_client/test/sfera/service/sfera_handshake_task_test.dart b/das_client/test/sfera/service/sfera_handshake_task_test.dart index 683feef1..7061eb66 100644 --- a/das_client/test/sfera/service/sfera_handshake_task_test.dart +++ b/das_client/test/sfera/service/sfera_handshake_task_test.dart @@ -3,6 +3,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/enums/das_driving_mode.dart'; +import 'package:das_client/sfera/src/model/sfera_g2b_reply_message.dart'; import 'package:das_client/sfera/src/service/task/handshake_task.dart'; import 'package:das_client/util/error_code.dart'; import 'package:fimber/fimber.dart'; 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 0205f6f9..6c9ff625 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/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'; import 'package:fimber/fimber.dart'; 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 3cc5d1e3..84bfb1cd 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 @@ -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/sfera_g2b_reply_message.dart'; import 'package:das_client/sfera/src/service/task/request_segment_profiles_task.dart'; import 'package:das_client/util/error_code.dart'; import 'package:fimber/fimber.dart'; diff --git a/das_client/test_resources/jp/SFERA_JP_9999.xml b/das_client/test_resources/jp/SFERA_JP_9999.xml new file mode 100644 index 00000000..f9ab6e13 --- /dev/null +++ b/das_client/test_resources/jp/SFERA_JP_9999.xml @@ -0,0 +1,65 @@ + + + + + 1085 + 9999 + 2022-01-04 + + + + + 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 new file mode 100644 index 00000000..cd66a541 --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_9999_1.xml @@ -0,0 +1,89 @@ + + + + 0085 + + + + + + + CH + 9991 + + + + + + block + + + + + laneChange + + + + + + + + + + + + + + + + + + + + CH + 9991 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_2.xml b/das_client/test_resources/sp/SFERA_SP_9999_2.xml new file mode 100644 index 00000000..418f201d --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_9999_2.xml @@ -0,0 +1,68 @@ + + + + 0085 + + + + + + CH + 9992 + + + + + + + entry + + + + + exit + + + + + + + + + + + + + + + + CH + 9992 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_3.xml b/das_client/test_resources/sp/SFERA_SP_9999_3.xml new file mode 100644 index 00000000..0266583b --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_9999_3.xml @@ -0,0 +1,62 @@ + + + + 0085 + + + + + + CH + 9993 + + + + + + intermediate + + + + + + + + + + + + + + + + CH + 9993 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_4.xml b/das_client/test_resources/sp/SFERA_SP_9999_4.xml new file mode 100644 index 00000000..7e08be86 --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_9999_4.xml @@ -0,0 +1,56 @@ + + + + 0085 + + + + + + CH + 9994 + + + + + + protection + + + + + + + + + CH + 9994 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/sp/SFERA_SP_9999_5.xml b/das_client/test_resources/sp/SFERA_SP_9999_5.xml new file mode 100644 index 00000000..1c8eb51b --- /dev/null +++ b/das_client/test_resources/sp/SFERA_SP_9999_5.xml @@ -0,0 +1,61 @@ + + + + 0085 + + + + + + + CH + 9995 + + + + + block + intermediate + + + + + + + + + + + + + + + CH + 9995 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_1.xml b/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_1.xml index 2aedc0c0..3e76cb2a 100644 --- a/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_1.xml +++ b/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_1.xml @@ -47,10 +47,6 @@ 9991 - - - - diff --git a/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_3.xml b/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_3.xml index c70c3ec1..0266583b 100644 --- a/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_3.xml +++ b/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_3.xml @@ -31,7 +31,7 @@ + TAF_TAP_location_type="stopping location"> CH 9993 diff --git a/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_5.xml b/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_5.xml index 23d32f55..1c8eb51b 100644 --- a/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_5.xml +++ b/sfera-mock/src/main/resources/static_sfera_resources/sp/SFERA_SP_9999_5.xml @@ -49,13 +49,13 @@ - + - + - +