Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added punctuality display (#122) #473

Draft
wants to merge 28 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1c6cc8d
feat: added CAB start and end signaling
rawi-coding Dec 4, 2024
f4dc23a
feat: handled track equipment segments that start or end outside of t…
rawi-coding Dec 6, 2024
2cd599a
Merge branch 'main' into 82-streckenausruestung
rawi-coding Dec 6, 2024
0f2d05b
chore: fixed some issues after merge.
rawi-coding Dec 6, 2024
7babb87
chore: added UI integration test for CAB signaling.
rawi-coding Dec 6, 2024
3313065
chore: changes from review and fixed ordering of CABSignaling.
rawi-coding Dec 10, 2024
d707eca
chore: fixed integration test.
rawi-coding Dec 10, 2024
d868b47
chore: added train journey to test non standard track equipment.
rawi-coding Dec 11, 2024
6a6428a
Merge branch 'main' into 82-streckenausruestung
rawi-coding Dec 12, 2024
638c975
chore: updated integration test for track equipment to T1 train journey
rawi-coding Dec 12, 2024
2fe61ea
fix ui tests
Grodien Dec 12, 2024
924ea83
fix tests for real
Grodien Dec 12, 2024
8987de9
feat: added visualization of non standard track equipment (#82).
rawi-coding Dec 17, 2024
b9939e6
Merge branch 'main' into 82-streckenausruestung
rawi-coding Dec 17, 2024
7698872
chore: add integration tests for track equipment
rawi-coding Dec 17, 2024
351dd8e
fix rendering issue
Grodien Dec 18, 2024
2046c2f
merge main
Grodien Dec 18, 2024
aef8aa8
quick fix for not throwing exception on event message
Grodien Dec 18, 2024
dbf1762
fix something I broke :)
Grodien Dec 18, 2024
fc76192
feat: implement basic sfera event handling
Grodien Dec 19, 2024
5bb0c58
chore: fix test
Grodien Dec 19, 2024
58c4347
merge main
Grodien Dec 19, 2024
0d9a432
fix lint
Grodien Dec 19, 2024
b1ec409
remove unused file
Grodien Dec 19, 2024
3bb680b
feat: Fixed the delay model and added all the needed Sfera models. Al…
tobe-official Dec 20, 2024
6dd34d7
Merge branch 'main' into 122-puenktlichkeit-anzeige
tobe-official Dec 20, 2024
a999728
fix: fixed the mentioned points. Still need to do the tests. SBBLoadi…
tobe-official Dec 23, 2024
d33253b
fix: fixed the tests and fixed the mentioned points
tobe-official Jan 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion das_client/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: d9dad56c0cd0b4fd8b4fe3034a53fd42a0b990f6

COCOAPODS: 1.16.1
COCOAPODS: 1.15.2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cocoapods bei dir noch updaten, dieser change reverten

2 changes: 1 addition & 1 deletion das_client/l10n/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"c_error_sfera_handshake_rejected": "Server hat die Verbindung abgelehnt",
"c_error_sfera_request_timeout": "Timeout bei der Anfrage",
"c_error_sfera_jp_unavailable": "Fahrordnung nicht vorhanden",
"c_error_sfera_sp_invalid": "Unvollständige Daten erhalten",
"c_error_sfera_invalid": "Unvollständige Daten erhalten",
"c_connection_track_weiche": "Weiche",
"c_button_confirm": "Übernehmen"
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,52 @@
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';
import 'package:das_client/app/bloc/train_journey_cubit.dart';
import 'package:flutter/material.dart';
import 'package:das_client/model/journey/journey.dart';
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';

class TimeContainer extends StatelessWidget {
const TimeContainer({super.key});

@override
Widget build(BuildContext context) {
return SBBGroup(
margin: const EdgeInsetsDirectional.fromSTEB(
sbbDefaultSpacing * 0.5,
0,
sbbDefaultSpacing * 0.5,
sbbDefaultSpacing,
),
padding: const EdgeInsets.all(sbbDefaultSpacing),
useShadow: false,
child: SizedBox(
width: 124.0,
height: 112.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'05:43:00',
style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0),
final bloc = context.trainJourneyCubit;

return StreamBuilder<Journey?>(
stream: bloc.journeyStream,
tobe-official marked this conversation as resolved.
Show resolved Hide resolved
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return Center(
child: SBBLoadingIndicator(),
);
}
final Journey journey = snapshot.data!;

return SBBGroup(
margin: const EdgeInsetsDirectional.fromSTEB(
sbbDefaultSpacing * 0.5,
0,
sbbDefaultSpacing * 0.5,
sbbDefaultSpacing,
),
_divider(),
Text(
'+00:01:30',
style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0),
padding: const EdgeInsets.all(sbbDefaultSpacing),
useShadow: false,
child: SizedBox(
width: 124.0,
height: 112.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'05:43:00',
style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0),
tobe-official marked this conversation as resolved.
Show resolved Hide resolved
),
_divider(),
_punctualityDisplay(journey),
],
),
),
],
),
),
);
);
});
}

Widget _divider() {
Expand All @@ -43,4 +55,15 @@ class TimeContainer extends StatelessWidget {
child: Divider(height: 1.0, color: SBBColors.cloud),
);
}

Widget _punctualityDisplay(Journey journey) {
final Duration delay = journey.metadata.delay!;
final String minutes = delay.inMinutes.abs().toString();
final String seconds = (delay.inSeconds.abs() % 60).toString();
final String formattedDuration = '${delay.isNegative ? '-' : '+'}$minutes:${seconds.padLeft(2, '0')}';
return Text(
formattedDuration,
style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0),
);
}
}
2 changes: 2 additions & 0 deletions das_client/lib/model/journey/metadata.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Metadata {
this.currentPosition,
this.routeStart,
this.routeEnd,
this.delay,
this.breakSeries,
this.additionalSpeedRestrictions = const [],
this.nonStandardTrackEquipmentSegments = const [],
Expand All @@ -21,6 +22,7 @@ class Metadata {
final List<AdditionalSpeedRestriction> additionalSpeedRestrictions;
final BaseData? routeStart;
final BaseData? routeEnd;
final Duration? delay;
final List<NonStandardTrackEquipmentSegment> nonStandardTrackEquipmentSegments;
final BreakSeries? breakSeries;
final Set<BreakSeries> availableBreakSeries;
Expand Down
31 changes: 27 additions & 4 deletions das_client/lib/sfera/src/mapper/sfera_model_mapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ import 'package:das_client/sfera/src/model/enums/xml_enum.dart';
import 'package:das_client/sfera/src/model/journey_profile.dart';
import 'package:das_client/sfera/src/model/multilingual_text.dart';
import 'package:das_client/sfera/src/model/network_specific_parameter.dart';
import 'package:das_client/sfera/src/model/related_train_information.dart';
import 'package:das_client/sfera/src/model/segment_profile.dart';
import 'package:das_client/sfera/src/model/speeds.dart';
import 'package:das_client/sfera/src/model/taf_tap_location.dart';
import 'package:das_client/sfera/src/model/train_characteristics.dart';
import 'package:fimber/fimber.dart';
import 'package:iso_duration/iso_duration.dart';

class SferaModelMapper {
SferaModelMapper._();
Expand All @@ -42,18 +44,21 @@ class SferaModelMapper {
static const String _protectionSectionNspFacultativeName = 'facultative';
static const String _protectionSectionNspLengthTypeName = 'lengthType';

static Journey mapToJourney(JourneyProfile journeyProfile, List<SegmentProfile> segmentProfiles,
List<TrainCharacteristics> trainCharacteristics) {
static Journey mapToJourney(
{required JourneyProfile journeyProfile,
List<SegmentProfile> segmentProfiles = const [],
List<TrainCharacteristics> trainCharacteristics = const [],
RelatedTrainInformation? relatedTrainInformation}) {
try {
return _mapToJourney(journeyProfile, segmentProfiles, trainCharacteristics);
return _mapToJourney(journeyProfile, segmentProfiles, trainCharacteristics, relatedTrainInformation);
} catch (e, s) {
Fimber.e('Error mapping journey-/segment profiles to journey:', ex: e, stacktrace: s);
return Journey.invalid();
}
}

static Journey _mapToJourney(JourneyProfile journeyProfile, List<SegmentProfile> segmentProfiles,
List<TrainCharacteristics> trainCharacteristics) {
List<TrainCharacteristics> trainCharacteristics, RelatedTrainInformation? relatedTrainInformation) {
final journeyData = <BaseData>[];

final segmentProfilesLists = journeyProfile.segmentProfilesLists.toList();
Expand Down Expand Up @@ -130,13 +135,17 @@ class SferaModelMapper {

final trainCharacteristic = _resolveFirstTrainCharacteristics(journeyProfile, trainCharacteristics);
final servicePoints = journeyData.where((it) => it.type == Datatype.servicePoint).toList();

return Journey(
metadata: Metadata(
nextStop: servicePoints.length > 1 ? servicePoints[1] as ServicePoint : null,
currentPosition: journeyData.first,
additionalSpeedRestrictions: additionalSpeedRestrictions,
routeStart: journeyData.firstOrNull,
routeEnd: journeyData.lastOrNull,
delay: _stringToDuration(relatedTrainInformation == null
? '+PT0M0S' //Base Value for when the information is Null
tobe-official marked this conversation as resolved.
Show resolved Hide resolved
: relatedTrainInformation.ownTrain.trainLocationInformation.delay.delay),
nonStandardTrackEquipmentSegments: trackEquipmentSegments,
availableBreakSeries: _parseAvailableBreakSeries(journeyData),
breakSeries: trainCharacteristic?.tcFeatures.trainCategoryCode != null &&
Expand All @@ -150,6 +159,20 @@ class SferaModelMapper {
);
}

static Duration _stringToDuration(String stringToChange) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ich würde diese Logik gleich im "Domain-Model" Delay führen, z.B. eine toDuration() Methode.

//'-PT41M30S'

//TODO code in util auslagern und ganz ganz viele tests wie zb -5 stunden
//TODO anschauen was mit über einer stunde geschehen sollte (mit UX)
final Duration? delay = tryParseIso8601Duration(stringToChange);

if (delay == null) {
//Todo change the error-code
throw FormatException('Invalid ISO 8601 duration format');
tobe-official marked this conversation as resolved.
Show resolved Hide resolved
}
return delay;
}

static List<AdditionalSpeedRestriction> _parseAdditionalSpeedRestrictions(
JourneyProfile journeyProfile, List<SegmentProfile> segmentProfiles) {
final List<AdditionalSpeedRestriction> result = [];
Expand Down
14 changes: 14 additions & 0 deletions das_client/lib/sfera/src/model/delay.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:das_client/sfera/src/model/sfera_xml_element.dart';

class Delay extends SferaXmlElement {
static const String elementType = 'Delay';

Delay({super.type = elementType, super.attributes, super.children, super.value});

String get delay => attributes['Delay']!;

@override
bool validate() {
return validateHasAttribute('Delay') && super.validate();
}
}
14 changes: 14 additions & 0 deletions das_client/lib/sfera/src/model/g2b_event_payload.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:das_client/sfera/src/model/journey_profile.dart';
import 'package:das_client/sfera/src/model/related_train_information.dart';
import 'package:das_client/sfera/src/model/sfera_xml_element.dart';

class G2bEventPayload extends SferaXmlElement {
static const String elementType = 'G2B_EventPayload';

G2bEventPayload({super.type = elementType, super.attributes, super.children, super.value});

RelatedTrainInformation? get relatedTrainInformation => children.whereType<RelatedTrainInformation>().firstOrNull;

Iterable<JourneyProfile> get journeyProfiles => children.whereType<JourneyProfile>();

}
3 changes: 3 additions & 0 deletions das_client/lib/sfera/src/model/g2b_reply_payload.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:das_client/sfera/src/model/journey_profile.dart';
import 'package:das_client/sfera/src/model/related_train_information.dart';
import 'package:das_client/sfera/src/model/segment_profile.dart';
import 'package:das_client/sfera/src/model/sfera_xml_element.dart';
import 'package:das_client/sfera/src/model/train_characteristics.dart';
Expand All @@ -13,4 +14,6 @@ class G2bReplyPayload extends SferaXmlElement {
Iterable<SegmentProfile> get segmentProfiles => children.whereType<SegmentProfile>();

Iterable<TrainCharacteristics> get trainCharacteristics => children.whereType<TrainCharacteristics>();

Iterable<RelatedTrainInformation> get relatedTrainInformation => children.whereType<RelatedTrainInformation>();
}
20 changes: 20 additions & 0 deletions das_client/lib/sfera/src/model/own_train.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:das_client/sfera/src/model/sfera_xml_element.dart';
import 'package:das_client/sfera/src/model/train_identification.dart';
import 'package:das_client/sfera/src/model/train_location_information.dart';

class OwnTrain extends SferaXmlElement {
static const String elementType = 'OwnTrain';

OwnTrain({super.type = elementType, super.attributes, super.children, super.value});

TrainIdentification get trainIdentification => children.whereType<TrainIdentification>().first;

TrainLocationInformation get trainLocationInformation => children.whereType<TrainLocationInformation>().first;

@override
bool validate() {
return validateHasChildOfType<TrainIdentification>() &&
validateHasChildOfType<TrainLocationInformation>() &&
super.validate();
}
}
16 changes: 16 additions & 0 deletions das_client/lib/sfera/src/model/related_train_information.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:das_client/sfera/src/model/sfera_xml_element.dart';

import 'package:das_client/sfera/src/model/own_train.dart';

class RelatedTrainInformation extends SferaXmlElement {
static const String elementType = 'RelatedTrainInformation';

RelatedTrainInformation({super.type = elementType, super.attributes, super.children, super.value});

OwnTrain get ownTrain => children.whereType<OwnTrain>().first;

@override
bool validate() {
return validateHasChildOfType<OwnTrain>() && super.validate();
}
}
18 changes: 18 additions & 0 deletions das_client/lib/sfera/src/model/sfera_g2b_event_message.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:das_client/sfera/src/model/g2b_event_payload.dart';
import 'package:das_client/sfera/src/model/message_header.dart';
import 'package:das_client/sfera/src/model/sfera_xml_element.dart';

class SferaG2bEventMessage extends SferaXmlElement {
static const String elementType = 'SFERA_G2B_EventMessage';

SferaG2bEventMessage({super.type = elementType, super.attributes, super.children, super.value});

MessageHeader get messageHeader => children.whereType<MessageHeader>().first;

G2bEventPayload? get payload => children.whereType<G2bEventPayload>().firstOrNull;

@override
bool validate() {
return validateHasChildOfType<MessageHeader>() && validateHasChildOfType<G2bEventPayload>() && super.validate();
}
}
16 changes: 16 additions & 0 deletions das_client/lib/sfera/src/model/train_location_information.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:das_client/sfera/src/model/delay.dart';
import 'package:das_client/sfera/src/model/sfera_xml_element.dart';

class TrainLocationInformation extends SferaXmlElement {
static const String elementType = 'TrainLocationInformation';

tobe-official marked this conversation as resolved.
Show resolved Hide resolved

TrainLocationInformation({super.type = elementType, super.attributes, super.children, super.value});

Delay get delay => children.whereType<Delay>().first;

@override
bool validate() {
return validateHasChildOfType<Delay>() && super.validate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:das_client/sfera/src/model/journey_profile.dart';
import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart';
import 'package:das_client/sfera/src/repo/sfera_repository.dart';
import 'package:das_client/sfera/src/service/event/sfera_event_message_handler.dart';
import 'package:fimber/fimber.dart';

class JourneyProfileEventHandler extends SferaEventMessageHandler<JourneyProfile> {
final SferaRepository _sferaRepository;

JourneyProfileEventHandler(super.onMessageHandled, this._sferaRepository);

@override
Future<bool> handleMessage(SferaG2bEventMessage eventMessage) async {
if (eventMessage.payload == null || eventMessage.payload!.journeyProfiles.isEmpty) {
return false;
}

Fimber.i('Updating journey profiles...');
for (final journeyProfile in eventMessage.payload!.journeyProfiles) {
await _sferaRepository.saveJourneyProfile(journeyProfile);
}

if (eventMessage.payload!.journeyProfiles.length > 1) {
Fimber.w('Received more then 1 journey profile which is not supported, using first one provided');
}

onMessageHandled(this, eventMessage.payload!.journeyProfiles.first);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:das_client/sfera/src/model/related_train_information.dart';
import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart';
import 'package:das_client/sfera/src/service/event/sfera_event_message_handler.dart';
import 'package:fimber/fimber.dart';

class RelatedTrainInformationEventHandler extends SferaEventMessageHandler<RelatedTrainInformation> {
RelatedTrainInformationEventHandler(super.onMessageHandled);

@override
Future<bool> handleMessage(SferaG2bEventMessage eventMessage) async {
if (eventMessage.payload == null || eventMessage.payload!.relatedTrainInformation == null) {
return false;
}

Fimber.i('Received new related train information... ${eventMessage.payload!.relatedTrainInformation?.ownTrain.trainLocationInformation.delay.delay}');
onMessageHandled(this, eventMessage.payload!.relatedTrainInformation!);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:das_client/sfera/src/model/sfera_g2b_event_message.dart';

typedef MessageHandled<T> = void Function(SferaEventMessageHandler handler, T data);

abstract class SferaEventMessageHandler<T> {
SferaEventMessageHandler(this.onMessageHandled);

final MessageHandled<T> onMessageHandled;

Future<bool> handleMessage(SferaG2bEventMessage eventMessage);
}
Loading
Loading