From 50dd6c19c9465e04d038466be23b9adccf79b6f6 Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Sat, 6 Aug 2022 00:27:46 +0300 Subject: [PATCH] Add cancelled trains to dep/arr board --- CHANGELOG.txt | 5 + lib/api/train_data.dart | 2 +- lib/main.dart | 48 +-- lib/models/station_data.dart | 13 +- lib/models/station_data.g.dart | 4 + lib/pages/main/main_page_cupertino.dart | 19 +- .../view_station/view_station.dart | 12 +- .../view_station/view_station_cupertino.dart | 4 +- .../view_station/view_station_material.dart | 329 ++++++------------ .../select_train/select_train.dart | 5 +- .../view_train/train_info.dart | 12 +- pubspec.yaml | 2 +- 12 files changed, 205 insertions(+), 250 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b716869..3641de8 100644 --- a/CHANGELOG.txt +++ b/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) diff --git a/lib/api/train_data.dart b/lib/api/train_data.dart index 40d22e1..3d3b091 100644 --- a/lib/api/train_data.dart +++ b/lib/api/train_data.dart @@ -5,7 +5,7 @@ import 'package:info_tren/models/train_data.dart'; Future 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); } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 58d74dc..cf37bb3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,8 +44,10 @@ Map 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), ); - } + // } } } diff --git a/lib/models/station_data.dart b/lib/models/station_data.dart index 4dbfd71..eb3cf08 100644 --- a/lib/models/station_data.dart +++ b/lib/models/station_data.dart @@ -35,8 +35,16 @@ class StationTrain { final String operator; final String terminus; final List? 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 json) => _$StationTrainFromJson(json); Map 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 json) => _$StationStatusFromJson(json); Map toJson() => _$StationStatusToJson(this); diff --git a/lib/models/station_data.g.dart b/lib/models/station_data.g.dart index 4953334..d351438 100644 --- a/lib/models/station_data.g.dart +++ b/lib/models/station_data.g.dart @@ -48,6 +48,7 @@ StationTrain _$StationTrainFromJson(Map json) => StationTrain( terminus: json['terminus'] as String, route: (json['route'] as List?)?.map((e) => e as String).toList(), + departureDate: DateTime.parse(json['departureDate'] as String), ); Map _$StationTrainToJson(StationTrain instance) => @@ -57,12 +58,14 @@ Map _$StationTrainToJson(StationTrain instance) => 'operator': instance.operator, 'terminus': instance.terminus, 'route': instance.route, + 'departureDate': instance.departureDate.toIso8601String(), }; StationStatus _$StationStatusFromJson(Map 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 _$StationStatusToJson(StationStatus instance) => { 'delay': instance.delay, 'real': instance.real, + 'cancelled': instance.cancelled, 'platform': instance.platform, }; diff --git a/lib/pages/main/main_page_cupertino.dart b/lib/pages/main/main_page_cupertino.dart index 968b4c6..f63e6bf 100644 --- a/lib/pages/main/main_page_cupertino.dart +++ b/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), diff --git a/lib/pages/station_arrdep_page/view_station/view_station.dart b/lib/pages/station_arrdep_page/view_station/view_station.dart index 910d81a..546e899 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station.dart +++ b/lib/pages/station_arrdep_page/view_station/view_station.dart @@ -34,8 +34,10 @@ abstract class ViewStationPageState extends State { 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 { }); } - 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 diff --git a/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart b/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart index 2f0d307..a170f1d 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart +++ b/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( diff --git a/lib/pages/station_arrdep_page/view_station/view_station_material.dart b/lib/pages/station_arrdep_page/view_station/view_station_material.dart index 426559e..6e1d41c 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station_material.dart +++ b/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); + } } diff --git a/lib/pages/train_info_page/select_train/select_train.dart b/lib/pages/train_info_page/select_train/select_train.dart index ba5f388..343c507 100644 --- a/lib/pages/train_info_page/select_train/select_train.dart +++ b/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 diff --git a/lib/pages/train_info_page/view_train/train_info.dart b/lib/pages/train_info_page/view_train/train_info.dart index 75bf744..9de1296 100644 --- a/lib/pages/train_info_page/view_train/train_info.dart +++ b/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( - 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; diff --git a/pubspec.yaml b/pubspec.yaml index 186120b..7d71e55 100644 --- a/pubspec.yaml +++ b/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"