Browse Source

Add cancelled trains to dep/arr board

master v2.7.8
Kenneth Bruen 2 years ago
parent
commit
50dd6c19c9
Signed by: kbruen
GPG Key ID: C1980A470C3EE5B1
  1. 5
      CHANGELOG.txt
  2. 2
      lib/api/train_data.dart
  3. 48
      lib/main.dart
  4. 13
      lib/models/station_data.dart
  5. 4
      lib/models/station_data.g.dart
  6. 19
      lib/pages/main/main_page_cupertino.dart
  7. 12
      lib/pages/station_arrdep_page/view_station/view_station.dart
  8. 4
      lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart
  9. 329
      lib/pages/station_arrdep_page/view_station/view_station_material.dart
  10. 5
      lib/pages/train_info_page/select_train/select_train.dart
  11. 12
      lib/pages/train_info_page/view_train/train_info.dart
  12. 2
      pubspec.yaml

5
CHANGELOG.txt

@ -1,3 +1,8 @@
v2.7.8
Added cancelled trains in departures/arrivals board.
Selecting train in departures/arrivels board chooses appropriate departure date.
Temporarily switched all platforms to Material.
v2.7.7
Improved departures/arrivals page:
- badge for platform (due to limitations in data, platform in only known when a train arrives/departs)

2
lib/api/train_data.dart

@ -5,7 +5,7 @@ import 'package:info_tren/models/train_data.dart';
Future<TrainData> getTrain(String trainNumber, {DateTime? date}) async {
date ??= DateTime.now();
final response = await http.get(Uri.https(authority, 'v2/train/$trainNumber', {
'date': date.toIso8601String(),
'date': date.toUtc().toIso8601String(),
}),);
return trainDataFromJson(response.body);
}

48
lib/main.dart

@ -44,8 +44,10 @@ Map<String, WidgetBuilder> routesByUiDesign(UiDesign uiDesign) => {
);
},
TrainInfo.routeName: (context) {
final args = ModalRoute.of(context)!.settings.arguments as TrainInfoArguments;
return TrainInfo(
trainNumber: ModalRoute.of(context)!.settings.arguments as String,
trainNumber: args.trainNumber,
date: args.date,
uiDesign: uiDesign,
);
},
@ -67,27 +69,27 @@ class StartPoint extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (Platform.isIOS || Platform.isMacOS) {
return AnnotatedRegion(
value: const SystemUiOverlayStyle(
statusBarBrightness: Brightness.dark,
),
child: CupertinoApp(
title: appTitle,
theme: CupertinoThemeData(
primaryColor: Colors.blue.shade600,
brightness: Brightness.dark,
// textTheme: CupertinoTextThemeData(
// textStyle: TextStyle(
// fontFamily: 'Atkinson Hyperlegible',
// ),
// ),
),
routes: routesByUiDesign(UiDesign.CUPERTINO),
),
);
}
else {
// if (Platform.isIOS || Platform.isMacOS) {
// return AnnotatedRegion(
// value: const SystemUiOverlayStyle(
// statusBarBrightness: Brightness.dark,
// ),
// child: CupertinoApp(
// title: appTitle,
// theme: CupertinoThemeData(
// primaryColor: Colors.blue.shade600,
// brightness: Brightness.dark,
// // textTheme: CupertinoTextThemeData(
// // textStyle: TextStyle(
// // fontFamily: 'Atkinson Hyperlegible',
// // ),
// // ),
// ),
// routes: routesByUiDesign(UiDesign.CUPERTINO),
// ),
// );
// }
// else {
return MaterialApp(
title: appTitle,
theme: ThemeData(
@ -102,6 +104,6 @@ class StartPoint extends StatelessWidget {
),
routes: routesByUiDesign(UiDesign.MATERIAL),
);
}
// }
}
}

13
lib/models/station_data.dart

@ -35,8 +35,16 @@ class StationTrain {
final String operator;
final String terminus;
final List<String>? route;
final DateTime departureDate;
StationTrain({required this.rank, required this.number, required this.operator, required this.terminus, this.route,});
StationTrain({
required this.rank,
required this.number,
required this.operator,
required this.terminus,
this.route,
required this.departureDate,
});
factory StationTrain.fromJson(Map<String, dynamic> json) => _$StationTrainFromJson(json);
Map<String, dynamic> toJson() => _$StationTrainToJson(this);
@ -46,9 +54,10 @@ class StationTrain {
class StationStatus {
final int delay;
final bool real;
final bool cancelled;
final String? platform;
StationStatus({required this.delay, required this.real, required this.platform});
StationStatus({required this.delay, required this.real, required this.cancelled, required this.platform});
factory StationStatus.fromJson(Map<String, dynamic> json) => _$StationStatusFromJson(json);
Map<String, dynamic> toJson() => _$StationStatusToJson(this);

4
lib/models/station_data.g.dart

@ -48,6 +48,7 @@ StationTrain _$StationTrainFromJson(Map<String, dynamic> json) => StationTrain(
terminus: json['terminus'] as String,
route:
(json['route'] as List<dynamic>?)?.map((e) => e as String).toList(),
departureDate: DateTime.parse(json['departureDate'] as String),
);
Map<String, dynamic> _$StationTrainToJson(StationTrain instance) =>
@ -57,12 +58,14 @@ Map<String, dynamic> _$StationTrainToJson(StationTrain instance) =>
'operator': instance.operator,
'terminus': instance.terminus,
'route': instance.route,
'departureDate': instance.departureDate.toIso8601String(),
};
StationStatus _$StationStatusFromJson(Map<String, dynamic> json) =>
StationStatus(
delay: json['delay'] as int,
real: json['real'] as bool,
cancelled: json['cancelled'] as bool,
platform: json['platform'] as String?,
);
@ -70,5 +73,6 @@ Map<String, dynamic> _$StationStatusToJson(StationStatus instance) =>
<String, dynamic>{
'delay': instance.delay,
'real': instance.real,
'cancelled': instance.cancelled,
'platform': instance.platform,
};

19
lib/pages/main/main_page_cupertino.dart

@ -39,7 +39,24 @@ class MainPageCupertino extends MainPageShared {
child: Column(
mainAxisSize: MainAxisSize.min,
children: options.map((option) => CupertinoButton.filled(
child: Text(option.name),
child: Text.rich(
TextSpan(
children: [
TextSpan(text: option.name),
if (option.description != null) ...[
TextSpan(text: '\n'),
TextSpan(
text: option.description,
style: TextStyle(
inherit: true,
fontSize: 14,
),
),
],
],
),
textAlign: TextAlign.center,
),
onPressed: option.action == null ? null : () => option.action!(context),
)).map((w) => Padding(
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2),

12
lib/pages/station_arrdep_page/view_station/view_station.dart

@ -34,8 +34,10 @@ abstract class ViewStationPageState extends State<ViewStationPage> {
static const loadingText = 'Se încarcă...';
static const arrivesFrom = 'Sosește de la';
static const arrivedFrom = 'A sosit de la';
static const cancelledArrival = 'Anulat - de la';
static const departsTo = 'Pleacă către';
static const departedTo = 'A plecat către';
static const cancelledDeparture = 'Anulat - către';
ViewStationPageTab tab = ViewStationPageTab.departures;
late String stationName;
@ -72,8 +74,14 @@ abstract class ViewStationPageState extends State<ViewStationPage> {
});
}
void onTrainTapped(String trainNumber) {
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: trainNumber);
void onTrainTapped(StationTrain train) {
Navigator.of(context).pushNamed(
TrainInfo.routeName,
arguments: TrainInfoArguments(
trainNumber: train.number,
date: train.departureDate,
),
);
}
@override

4
lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart

@ -54,7 +54,7 @@ class ViewStationPageStateCupertino extends ViewStationPageState {
@override
Widget buildStationArrivalItem(BuildContext context, StationArrDep item) {
return GestureDetector(
onTap: () => onTrainTapped(item.train.number),
onTap: () => onTrainTapped(item.train),
child: CupertinoFormRow(
child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
prefix: Text.rich(
@ -87,7 +87,7 @@ class ViewStationPageStateCupertino extends ViewStationPageState {
@override
Widget buildStationDepartureItem(BuildContext context, StationArrDep item) {
return GestureDetector(
onTap: () => onTrainTapped(item.train.number),
onTap: () => onTrainTapped(item.train),
child: CupertinoFormRow(
child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
prefix: Text.rich(

329
lib/pages/station_arrdep_page/view_station/view_station_material.dart

@ -49,241 +49,140 @@ class ViewStationPageStateMaterial extends ViewStationPageState {
);
}
@override
Widget buildStationArrivalItem(BuildContext context, StationArrDep item) {
Widget buildStationItem(BuildContext context, StationArrDep item, {required bool arrival}) {
return InkWell(
onTap: () => onTrainTapped(item.train.number),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}',
style: TextStyle(
inherit: true,
fontFeatures: [
FontFeature.tabularFigures(),
],
decoration: item.status.delay != 0 ? TextDecoration.lineThrough : null,
fontSize: item.status.delay != 0 ? 12 : null,
onTap: () => onTrainTapped(item.train),
child: Container(
color: item.status.cancelled ? Colors.red.withAlpha(100) : null,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}',
style: TextStyle(
inherit: true,
fontFeatures: [
FontFeature.tabularFigures(),
],
decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null,
fontSize: item.status.delay != 0 ? 12 : null,
),
),
),
if (item.status.delay != 0) Builder(
builder: (context) {
final newTime = item.time.add(Duration(minutes: item.status.delay));
final delay = item.status.delay > 0;
if (item.status.delay != 0) Builder(
builder: (context) {
final newTime = item.time.add(Duration(minutes: item.status.delay));
final delay = item.status.delay > 0;
return Text(
'${newTime.toLocal().hour.toString().padLeft(2, '0')}:${newTime.toLocal().minute.toString().padLeft(2, '0')}',
style: TextStyle(
inherit: true,
fontFeatures: [
FontFeature.tabularFigures(),
],
color: delay ? Colors.red : Colors.green,
),
);
},
),
],
),
),
Expanded(
child: IgnorePointer(
child: ListTile(
isThreeLine: item.status.delay != 0,
title: Text.rich(
TextSpan(
children: [
TextSpan(
text: item.train.rank,
return Text(
'${newTime.toLocal().hour.toString().padLeft(2, '0')}:${newTime.toLocal().minute.toString().padLeft(2, '0')}',
style: TextStyle(
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
inherit: true,
fontFeatures: [
FontFeature.tabularFigures(),
],
color: delay ? Colors.red : Colors.green,
),
),
TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
),
subtitle: Text.rich(
TextSpan(
children: [
TextSpan(text: item.time.add(Duration(minutes: max(0, item.status.delay))).compareTo(DateTime.now()) < 0 ? ViewStationPageState.arrivedFrom : ViewStationPageState.arrivesFrom),
TextSpan(text: ' '),
TextSpan(text: item.train.terminus),
if (item.status.delay != 0) ...[
TextSpan(text: '\n'),
if (item.status.delay.abs() >= 60) ...[
TextSpan(text: (item.status.delay.abs() ~/ 60).toString()),
TextSpan(text: item.status.delay.abs() >= 120 ? ' ore' : ' oră'),
if (item.status.delay.abs() % 60 != 0)
TextSpan(text: ' și '),
],
TextSpan(text: (item.status.delay.abs() % 60).toString()),
TextSpan(text: item.status.delay.abs() > 1 ? ' minute' : ' minut'),
TextSpan(text: ' '),
if (item.status.delay > 0)
TextSpan(
text: 'întârziere',
style: TextStyle(
inherit: true,
color: Colors.red,
),
)
else
TextSpan(
text: 'mai devreme',
style: TextStyle(
inherit: true,
color: Colors.green,
),
),
],
],
);
},
),
),
),
),
),
if (item.status.platform != null)
IntrinsicHeight(
child: AspectRatio(
aspectRatio: 1,
child: MaterialBadge(
text: item.status.platform!,
caption: 'Linia',
isOnTime: item.status.real && item.status.delay <= 0,
isDelayed: item.status.real && item.status.delay > 0,
),
],
),
),
],
),
);
}
@override
Widget buildStationDepartureItem(BuildContext context, StationArrDep item) {
return InkWell(
onTap: () => onTrainTapped(item.train.number),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}',
style: TextStyle(
inherit: true,
fontFeatures: [
FontFeature.tabularFigures(),
],
decoration: item.status.delay != 0 ? TextDecoration.lineThrough : null,
fontSize: item.status.delay != 0 ? 12 : null,
),
),
if (item.status.delay != 0) Builder(
builder: (context) {
final newTime = item.time.add(Duration(minutes: item.status.delay));
final delay = item.status.delay > 0;
return Text(
'${newTime.toLocal().hour.toString().padLeft(2, '0')}:${newTime.toLocal().minute.toString().padLeft(2, '0')}',
style: TextStyle(
inherit: true,
fontFeatures: [
FontFeature.tabularFigures(),
],
color: delay ? Colors.red : Colors.green,
),
);
},
),
],
),
),
Expanded(
child: IgnorePointer(
child: ListTile(
isThreeLine: item.status.delay != 0,
title: Text.rich(
TextSpan(
children: [
TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
Expanded(
child: IgnorePointer(
child: ListTile(
isThreeLine: item.status.delay != 0,
title: Text.rich(
TextSpan(
children: [
TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
),
),
),
TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
),
),
subtitle: Text.rich(
TextSpan(
children: [
TextSpan(text: item.time.add(Duration(minutes: max(0, item.status.delay))).compareTo(DateTime.now()) < 0 ? ViewStationPageState.departedTo : ViewStationPageState.departsTo),
TextSpan(text: ' '),
TextSpan(text: item.train.terminus),
if (item.status.delay != 0) ...[
TextSpan(text: '\n'),
if (item.status.delay.abs() >= 60) ...[
TextSpan(text: (item.status.delay.abs() ~/ 60).toString()),
TextSpan(text: item.status.delay.abs() >= 120 ? ' ore' : ' oră'),
if (item.status.delay.abs() % 60 != 0)
TextSpan(text: ' și '),
],
TextSpan(text: (item.status.delay.abs() % 60).toString()),
TextSpan(text: item.status.delay.abs() > 1 ? ' minute' : ' minut'),
subtitle: Text.rich(
TextSpan(
children: [
TextSpan(
text: item.status.cancelled
? (arrival ? ViewStationPageState.cancelledArrival : ViewStationPageState.cancelledDeparture)
: item.time.add(Duration(minutes: max(0, item.status.delay))).compareTo(DateTime.now()) < 0
? (arrival ? ViewStationPageState.arrivedFrom : ViewStationPageState.departedTo)
: (arrival ? ViewStationPageState.arrivesFrom : ViewStationPageState.departsTo)
),
TextSpan(text: ' '),
if (item.status.delay > 0)
TextSpan(
text: 'întârziere',
style: TextStyle(
inherit: true,
color: Colors.red,
),
)
else
TextSpan(
text: 'mai devreme',
style: TextStyle(
inherit: true,
color: Colors.green,
TextSpan(text: item.train.terminus),
if (item.status.delay != 0) ...[
TextSpan(text: '\n'),
if (item.status.delay.abs() >= 60) ...[
TextSpan(text: (item.status.delay.abs() ~/ 60).toString()),
TextSpan(text: item.status.delay.abs() >= 120 ? ' ore' : ' oră'),
if (item.status.delay.abs() % 60 != 0)
TextSpan(text: ' și '),
],
TextSpan(text: (item.status.delay.abs() % 60).toString()),
TextSpan(text: item.status.delay.abs() > 1 ? ' minute' : ' minut'),
TextSpan(text: ' '),
if (item.status.delay > 0)
TextSpan(
text: 'întârziere',
style: TextStyle(
inherit: true,
color: Colors.red,
),
)
else
TextSpan(
text: 'mai devreme',
style: TextStyle(
inherit: true,
color: Colors.green,
),
),
),
],
],
],
),
),
),
),
),
),
if (item.status.platform != null)
IntrinsicHeight(
child: AspectRatio(
aspectRatio: 1,
child: MaterialBadge(
text: item.status.platform!,
caption: 'Linia',
isOnTime: item.status.real && item.status.delay <= 0,
isDelayed: item.status.real && item.status.delay > 0,
if (item.status.platform != null)
IntrinsicHeight(
child: AspectRatio(
aspectRatio: 1,
child: MaterialBadge(
text: item.status.platform!,
caption: 'Linia',
isOnTime: item.status.real && item.status.delay <= 0,
isDelayed: item.status.real && item.status.delay > 0,
),
),
),
),
],
],
),
),
);
}
@override
Widget buildStationArrivalItem(BuildContext context, StationArrDep item) {
return buildStationItem(context, item, arrival: true);
}
@override
Widget buildStationDepartureItem(BuildContext context, StationArrDep item) {
return buildStationItem(context, item, arrival: false);
}
}

5
lib/pages/train_info_page/select_train/select_train.dart

@ -23,7 +23,10 @@ class SelectTrainPage extends StatefulWidget {
void onTrainSelected(BuildContext context, String selection) {
selection = selection.characters.takeWhile((char) => List.generate(10, (i) => i.toString()).contains(char)).join();
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: selection);
Navigator.of(context).pushNamed(
TrainInfo.routeName,
arguments: TrainInfoArguments(trainNumber: selection),
);
}
@override

12
lib/pages/train_info_page/view_train/train_info.dart

@ -16,15 +16,16 @@ class TrainInfo extends StatelessWidget {
final UiDesign? uiDesign;
final String trainNumber;
final DateTime? date;
TrainInfo({Key? key, required this.trainNumber, this.uiDesign}): super(key: key);
TrainInfo({Key? key, required this.trainNumber, this.date, this.uiDesign}): super(key: key);
@override
Widget build(BuildContext context) {
final uiDesign = this.uiDesign ?? defaultUiDesign;
return RefreshFutureBuilder<TrainData>(
futureCreator: () => getTrain(trainNumber),
futureCreator: () => getTrain(trainNumber, date: date),
builder: (context, refresh, replaceFutureBuilder, snapshot) {
void onViewYesterdayTrain() {
replaceFutureBuilder(() => getTrain(trainNumber, date: DateTime.now().subtract(const Duration(days: 1))));
@ -66,6 +67,13 @@ class TrainInfo extends StatelessWidget {
}
}
class TrainInfoArguments {
final String trainNumber;
final DateTime? date;
TrainInfoArguments({required this.trainNumber, this.date});
}
abstract class TrainInfoLoading extends StatelessWidget {
final String title;
final Widget loadingWidget;

2
pubspec.yaml

@ -11,7 +11,7 @@ description: O aplicație de vizualizare a datelor puse la dispoziție de Inform
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 2.7.7
version: 2.7.8
environment:
sdk: ">=2.15.0 <3.0.0"

Loading…
Cancel
Save