Skip to content

Commit

Permalink
feat: added graduated station speed. (#82) (#484)
Browse files Browse the repository at this point in the history
  • Loading branch information
rawi-coding authored Jan 15, 2025
1 parent 27379a2 commit 8af6cf9
Show file tree
Hide file tree
Showing 28 changed files with 840 additions and 58 deletions.
60 changes: 60 additions & 0 deletions das_client/integration_test/test/train_journey_table_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:das_client/app/pages/journey/train_journey/widgets/table/additio
import 'package:das_client/app/pages/journey/train_journey/widgets/table/balise_row.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cab_signaling_row.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/bracket_station_cell_body.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/graduated_speeds_cell_body.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/curve_point_row.dart';
Expand Down Expand Up @@ -768,6 +769,65 @@ void main() {
// check ExtendedSpeedReversingImpossibleKey in Flughafen
_checkTrackEquipmentOnServicePoint('Flughafen', TrackEquipmentCellBody.extendedSpeedReversingPossibleKey);
});

testWidgets('test if station speeds are displayed correctly', (tester) async {
await prepareAndStartApp(tester);

// load train journey by filling out train selection page
await _loadTrainJourney(tester, trainNumber: 'T8');

final scrollableFinder = find.byType(ListView);
expect(scrollableFinder, findsOneWidget);

// check station speeds for Bern

final bernStationRow = findDASTableRowByText('Bern');
expect(bernStationRow, findsOneWidget);
final bernIncomingSpeeds = find.descendant(of: bernStationRow, matching: find.byKey(GraduatedSpeedsCellBody.incomingSpeedsKey));
expect(bernIncomingSpeeds, findsOneWidget);
final bernIncomingSpeedsText = find.descendant(of: bernStationRow, matching: find.text('75-70-60'));
expect(bernIncomingSpeedsText, findsOneWidget);
final bernOutgoingSpeeds = find.descendant(of: bernStationRow, matching: find.byKey(GraduatedSpeedsCellBody.outgoingSpeedsKey));
expect(bernOutgoingSpeeds, findsNothing);

// check station speeds for Wankdorf, no station speeds given

final wankdorfStationRow = findDASTableRowByText('Wankdorf');
expect(wankdorfStationRow, findsOneWidget);
final wankdorfIncomingSpeeds = find.descendant(of: wankdorfStationRow, matching: find.byKey(GraduatedSpeedsCellBody.incomingSpeedsKey));
expect(wankdorfIncomingSpeeds, findsNothing);
final wankdorfOutgoingSpeeds = find.descendant(of: wankdorfStationRow, matching: find.byKey(GraduatedSpeedsCellBody.outgoingSpeedsKey));
expect(wankdorfOutgoingSpeeds, findsNothing);

// check station speeds for Burgdorf

final burgdorfStationRow = findDASTableRowByText('Burgdorf');
expect(burgdorfStationRow, findsOneWidget);
final burgdorfIncomingSpeeds = find.descendant(of: burgdorfStationRow, matching: find.byKey(GraduatedSpeedsCellBody.incomingSpeedsKey));
expect(burgdorfIncomingSpeeds, findsOneWidget);
final burgdorfIncomingSpeeds75 = find.descendant(of: burgdorfIncomingSpeeds, matching: find.text('75'));
expect(burgdorfIncomingSpeeds75, findsOneWidget);
final burgdorfIncomingSpeeds70 = find.descendant(of: burgdorfIncomingSpeeds, matching: find.text('70'));
expect(burgdorfIncomingSpeeds70, findsOneWidget);
final burgdorfIncomingSpeeds70Circled = find.ancestor(of: burgdorfIncomingSpeeds70, matching: find.byKey(GraduatedSpeedsCellBody.circledSpeedKey));
expect(burgdorfIncomingSpeeds70Circled, findsOneWidget);
final burgdorfOutgoingSpeeds = find.descendant(of: burgdorfStationRow, matching: find.byKey(GraduatedSpeedsCellBody.outgoingSpeedsKey));
expect(burgdorfOutgoingSpeeds, findsOneWidget);
final burgdorfOutgoingSpeeds60 = find.descendant(of: burgdorfOutgoingSpeeds, matching: find.text('60'));
expect(burgdorfOutgoingSpeeds60, findsOneWidget);
final burgdorfOutgoingSpeeds60Squared = find.ancestor(of: burgdorfOutgoingSpeeds60, matching: find.byKey(GraduatedSpeedsCellBody.squaredSpeedKey));
expect(burgdorfOutgoingSpeeds60Squared, findsOneWidget);

// check station speeds for Olten, no graduated speed for train series R

final oltenStationRow = findDASTableRowByText('Olten');
expect(oltenStationRow, findsOneWidget);
final oltenIncomingSpeeds = find.descendant(of: oltenStationRow, matching: find.byKey(GraduatedSpeedsCellBody.incomingSpeedsKey));
expect(oltenIncomingSpeeds, findsNothing);
final oltenOutgoingSpeeds = find.descendant(of: oltenStationRow, matching: find.byKey(GraduatedSpeedsCellBody.outgoingSpeedsKey));
expect(oltenOutgoingSpeeds, findsNothing);

});
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import 'package:das_client/app/pages/journey/train_journey/widgets/header/adl_notification.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/header/header.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/train_journey.dart';
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';
import 'package:flutter/material.dart';

// TODO: handle extraLarge font sizes (diff to figma) globally.
// TODO: Add testing
class TrainJourneyOverview extends StatelessWidget {
const TrainJourneyOverview({super.key});

Expand All @@ -14,10 +11,6 @@ class TrainJourneyOverview extends StatelessWidget {
return const Column(
children: [
Header(),
ADLNotification(
message: 'VMax fahren bis Wettingen',
margin: EdgeInsets.fromLTRB(sbbDefaultSpacing * 0.5, 0, sbbDefaultSpacing * 0.5, sbbDefaultSpacing),
),
Expanded(child: TrainJourney()),
],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import 'package:das_client/app/pages/journey/train_journey/widgets/header/depart
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/das_text_styles.dart';
import 'package:das_client/app/widgets/widget_extensions.dart';
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';
import 'package:das_client/model/journey/journey.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:das_client/model/journey/journey.dart';
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';

class MainContainer extends StatelessWidget {
const MainContainer({super.key});
Expand Down Expand Up @@ -93,6 +92,7 @@ class MainContainer extends StatelessWidget {
Widget _buttonArea() {
return Builder(builder: (context) {
return Row(
spacing: sbbDefaultSpacing * 0.5,
children: [
SBBTertiaryButtonLarge(
label: context.l10n.p_train_journey_header_button_dark_theme,
Expand All @@ -108,7 +108,7 @@ class MainContainer extends StatelessWidget {
icon: SBBIcons.context_menu_small,
onPressed: () {},
),
].withSpacing(width: sbbDefaultSpacing * 0.5),
],
);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AdditionalSpeedRestrictionRow extends BaseRowBuilder<AdditionalSpeedRestri
}

@override
DASTableCell graduatedSpeedCell(BuildContext context) {
DASTableCell localSpeedCell(BuildContext context) {
return DASTableCell(
child: Text(data.restriction.speed.toString()),
alignment: Alignment.center,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class BaseRowBuilder<T extends BaseData> extends DASTableRowBuilder {
informationCell(context),
iconsCell2(context),
iconsCell3(context),
graduatedSpeedCell(context),
localSpeedCell(context),
brakedWeightSpeedCell(context),
advisedSpeedCell(context),
actionsCell(context),
Expand All @@ -63,16 +63,17 @@ class BaseRowBuilder<T extends BaseData> extends DASTableRowBuilder {
}

return DASTableCell(
color: specialCellColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(data.kilometre[0].toStringAsFixed(1)),
if (data.kilometre.length > 1) Text(data.kilometre[1].toStringAsFixed(1))
],
),
alignment: Alignment.centerLeft);
color: specialCellColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(data.kilometre[0].toStringAsFixed(1)),
if (data.kilometre.length > 1) Text(data.kilometre[1].toStringAsFixed(1))
],
),
alignment: Alignment.centerLeft,
);
}

DASTableCell routeCell(BuildContext context) {
Expand Down Expand Up @@ -107,7 +108,7 @@ class BaseRowBuilder<T extends BaseData> extends DASTableRowBuilder {
return DASTableCell.empty();
}

DASTableCell graduatedSpeedCell(BuildContext context) {
DASTableCell localSpeedCell(BuildContext context) {
return DASTableCell.empty();
}

Expand All @@ -129,17 +130,14 @@ class BaseRowBuilder<T extends BaseData> extends DASTableRowBuilder {
);
}

// TODO: clarify use of different icon cells and set appropriate name
DASTableCell iconsCell1(BuildContext context) {
return DASTableCell.empty();
}

// TODO: clarify use of different icon cells and set appropriate name
DASTableCell iconsCell2(BuildContext context) {
return DASTableCell.empty();
}

// TODO: clarify use of different icon cells and set appropriate name
DASTableCell iconsCell3(BuildContext context) {
return DASTableCell.empty();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'package:das_client/app/widgets/table/das_table_theme.dart';
import 'package:das_client/app/widgets/widget_extensions.dart';
import 'package:das_client/model/journey/speed.dart';
import 'package:flutter/material.dart';
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';

class GraduatedSpeedsCellBody extends StatelessWidget {
static const Key incomingSpeedsKey = Key('incoming-speeds');
static const Key outgoingSpeedsKey = Key('outgoing-speeds');
static const Key circledSpeedKey = Key('graduated-speed-circled');
static const Key squaredSpeedKey = Key('graduated-speed-squared');

const GraduatedSpeedsCellBody({
this.incomingSpeeds = const [],
this.outgoingSpeeds = const [],
super.key,
});

final List<Speed> incomingSpeeds;
final List<Speed> outgoingSpeeds;

@override
Widget build(BuildContext context) {
if (outgoingSpeeds.isNotEmpty) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_visualizedSpeeds(key: incomingSpeedsKey, speeds: incomingSpeeds),
Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Divider(color: Theme.of(context).colorScheme.onSurface, height: 1.0),
),
_visualizedSpeeds(key: outgoingSpeedsKey, speeds: outgoingSpeeds),
],
);
}

return _visualizedSpeeds(key: incomingSpeedsKey, speeds: incomingSpeeds);
}

Widget _visualizedSpeeds({required List<Speed> speeds, Key? key}) {
if (speeds.hasSquaredOrCircled) {
return Row(
key: key,
mainAxisSize: MainAxisSize.min,
children: speeds.map((speed) => _speedText(speed)).withDivider(Text('-')).toList(),
);
}
return Text(key: key, speeds.toJoinedString());
}

Widget _speedText(Speed speed) {
return Builder(builder: (context) {
final squaredOrCircled = speed.isCircled || speed.isSquared;
return Container(
key: squaredOrCircled ? (speed.isCircled ? circledSpeedKey : squaredSpeedKey) : null,
padding: EdgeInsets.all(1.0),
decoration: squaredOrCircled
? BoxDecoration(
border: Border.all(color: Theme.of(context).colorScheme.onSurface),
borderRadius: speed.isCircled ? BorderRadius.circular(sbbDefaultSpacing) : BorderRadius.zero,
)
: null,
child: Text(
speed.speed.toString(),
style: DASTableTheme.of(context)?.data.dataTextStyle?.copyWith(height: 0),
),
);
});
}
}

// extensions

extension _SpeedIterableX on Iterable<Speed> {
String toJoinedString({String divider = '-'}) => map((it) => it.speed.toString()).join(divider);

bool get hasSquaredOrCircled => any((it) => it.isSquared || it.isCircled);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ class CurvePointRow extends BaseRowBuilder<CurvePoint> {
super.trackEquipmentRenderData,
});

@override
DASTableCell brakedWeightSpeedCell(BuildContext context) {
return DASTableCell.empty();
}

@override
DASTableCell localSpeedCell(BuildContext context) {
return super.brakedWeightSpeedCell(context);
}

@override
DASTableCell informationCell(BuildContext context) {
return DASTableCell(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
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_cell_body.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/graduated_speeds_cell_body.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/route_cell_body.dart';
import 'package:das_client/app/pages/journey/train_journey/widgets/table/cells/track_equipment_cell_body.dart';
import 'package:das_client/app/widgets/assets.dart';
import 'package:das_client/app/widgets/das_text_styles.dart';
import 'package:das_client/app/widgets/table/das_table_cell.dart';
import 'package:das_client/model/journey/service_point.dart';
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart';

class ServicePointRow extends BaseRowBuilder<ServicePoint> {
static const Key stopOnRequestKey = Key('stop_on_request_key');
Expand Down Expand Up @@ -51,18 +52,8 @@ class ServicePointRow extends BaseRowBuilder<ServicePoint> {
child: Stack(
clipBehavior: Clip.none,
children: [
if (data.bracketStation != null)
BracketStationCellBody(
bracketStation: data.bracketStation!,
height: height,
),
if (!data.mandatoryStop)
Align(
alignment: Alignment.bottomCenter,
child: SvgPicture.asset(
AppAssets.iconStopOnRequest,
key: stopOnRequestKey,
))
if (data.bracketStation != null) _bracketStationCell(),
if (!data.mandatoryStop) _stopOnRequestIcon()
],
),
);
Expand All @@ -84,6 +75,25 @@ class ServicePointRow extends BaseRowBuilder<ServicePoint> {
);
}

@override
DASTableCell localSpeedCell(BuildContext context) {
if (data.stationSpeedData == null) return DASTableCell.empty();

final currentTrainSeries = settings.selectedBreakSeries?.trainSeries ?? metadata.breakSeries?.trainSeries;

final graduatedSpeeds = data.stationSpeedData!.graduatedSpeedsFor(currentTrainSeries);
if (graduatedSpeeds == null) return DASTableCell.empty();

return DASTableCell(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: sbbDefaultSpacing * 0.5),
child: GraduatedSpeedsCellBody(
incomingSpeeds: graduatedSpeeds.incomingSpeeds,
outgoingSpeeds: graduatedSpeeds.outgoingSpeeds,
),
);
}

@override
DASTableCell trackEquipment(BuildContext context) {
return DASTableCell(
Expand All @@ -95,4 +105,21 @@ class ServicePointRow extends BaseRowBuilder<ServicePoint> {
),
);
}

Widget _stopOnRequestIcon() {
return Align(
alignment: Alignment.bottomCenter,
child: SvgPicture.asset(
AppAssets.iconStopOnRequest,
key: stopOnRequestKey,
),
);
}

Widget _bracketStationCell() {
return BracketStationCellBody(
bracketStation: data.bracketStation!,
height: height,
);
}
}
6 changes: 3 additions & 3 deletions das_client/lib/app/widgets/widget_extensions.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';

extension SpacingExtension on List<Widget> {
withSpacing({double? width, double? height}) {
return expand((x) => [SizedBox(width: width, height: height), x])
extension WidgetListExtension on Iterable<Widget> {
withDivider(final Widget divider) {
return expand((x) => [divider, x])
.skip(1)
.toList();
}
Expand Down
Loading

0 comments on commit 8af6cf9

Please sign in to comment.