diff --git a/.vscode/launch.json b/.vscode/launch.json index 83d9b10..b0bdb95 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -26,12 +26,28 @@ "request": "launch", "type": "dart", "deviceId": "iphone" + }, + { + "name": "macOS", + "request": "launch", + "type": "dart", + "deviceId": "macOS" + }, + { + "name": "Samsung", + "request": "launch", + "type": "dart", + "deviceId": "SM" } ], "compounds": [ { "name": "All Devices", - "configurations": ["Android Emulator", "iPhone"] + "configurations": ["Android Emulator", "iPhone", "macOS"] + }, + { + "name": "Mac + Samsung", + "configurations": ["macOS", "Samsung"] } ] } \ No newline at end of file diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT index 2a2b234..fca8ac9 100644 --- a/CHANGELOG.TXT +++ b/CHANGELOG.TXT @@ -1,3 +1,7 @@ +v2.4.0 +Moved to api v2 +Added support for any train number, including train numbers starting with 0 + v2.3.1 Fixed badge background when arrival is known but not departure diff --git a/lib/api/train_data.dart b/lib/api/train_data.dart index 33b8c5b..c05fd4b 100644 --- a/lib/api/train_data.dart +++ b/lib/api/train_data.dart @@ -4,6 +4,6 @@ import 'package:info_tren/models/train_data.dart'; const AUTHORITY = 'scraper.infotren.dcdevelop.xyz'; Future getTrain(String trainNumber) async { - final response = await http.get(Uri.https(AUTHORITY, 'train/$trainNumber')); + final response = await http.get(Uri.https(AUTHORITY, 'v2/train/$trainNumber')); return trainDataFromJson(response.body); } \ No newline at end of file diff --git a/lib/components/slim_app_bar.dart b/lib/components/slim_app_bar.dart index 7e74411..64b2ba7 100644 --- a/lib/components/slim_app_bar.dart +++ b/lib/components/slim_app_bar.dart @@ -18,7 +18,7 @@ class SlimAppBar extends StatelessWidget { height: size, child: Container( color: - Theme.of(context).appBarTheme.color ?? + Theme.of(context).appBarTheme.backgroundColor ?? Theme.of(context).primaryColor, child: InkWell( onTap: (ModalRoute.of(context)?.canPop ?? false) 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 d358d5d..42561b5 100644 --- a/lib/pages/train_info_page/view_train/train_info.dart +++ b/lib/pages/train_info_page/view_train/train_info.dart @@ -30,7 +30,7 @@ class TrainInfo extends StatelessWidget { switch (uiDesign) { case UiDesign.MATERIAL: if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) { - return TrainInfoLoadingMaterial(title: trainNumber.toString(),); + return TrainInfoLoadingMaterial(title: trainNumber.toString(), loadingText: "Se încarcă...",); } else if (snapshot.state == RefreshFutureBuilderState.error) { return TrainInfoErrorMaterial(title: '$trainNumber - Error', error: snapshot.error!, refresh: refresh,); @@ -39,13 +39,13 @@ class TrainInfo extends StatelessWidget { return TrainInfoMaterial(trainData: snapshot.data!, refresh: refresh,); case UiDesign.CUPERTINO: if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) { - return TrainInfoLoadingCupertino(title: trainNumber.toString(),); + return TrainInfoLoadingCupertino(title: trainNumber.toString(), loadingText: "Se încarcă...",); } else if (snapshot.state == RefreshFutureBuilderState.error) { return TrainInfoErrorCupertino(title: '$trainNumber - Error', error: snapshot.error!, refresh: refresh,); } - return TrainInfoCupertino(trainData: snapshot.data!, refresh: refresh,); + return TrainInfoCupertino(trainData: snapshot.data!, refresh: refresh, isRefreshing: snapshot.state == RefreshFutureBuilderState.refreshing,); default: throw UnmatchedUiDesignException(uiDesign); } diff --git a/lib/pages/train_info_page/view_train/train_info_cupertino.dart b/lib/pages/train_info_page/view_train/train_info_cupertino.dart index 754aa97..9ba42d9 100644 --- a/lib/pages/train_info_page/view_train/train_info_cupertino.dart +++ b/lib/pages/train_info_page/view_train/train_info_cupertino.dart @@ -11,7 +11,11 @@ import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino_ import 'package:info_tren/utils/state_to_string.dart'; class TrainInfoLoadingCupertino extends TrainInfoLoading { - TrainInfoLoadingCupertino({required String title, String? loadingText}) : super(title: title, loadingText: loadingText, uiDesign: UiDesign.CUPERTINO); + TrainInfoLoadingCupertino({required String title, String? loadingText}) + : super( + title: title, + loadingText: loadingText, + uiDesign: UiDesign.CUPERTINO); @override Widget build(BuildContext context) { @@ -28,10 +32,14 @@ class TrainInfoLoadingCupertino extends TrainInfoLoading { class TrainInfoErrorCupertino extends TrainInfoError { TrainInfoErrorCupertino({ - required Object error, + required Object error, required String title, Future Function()? refresh, - }) : super(error: error, title: title, refresh: refresh,); + }) : super( + error: error, + title: title, + refresh: refresh, + ); @override Widget build(BuildContext context) { @@ -62,174 +70,210 @@ class TrainInfoErrorCupertino extends TrainInfoError { class TrainInfoCupertino extends StatelessWidget { final TrainData trainData; final Future Function()? refresh; + final bool? isRefreshing; - TrainInfoCupertino({required this.trainData, this.refresh,}); + TrainInfoCupertino({ + required this.trainData, + this.refresh, + this.isRefreshing, + }); @override Widget build(BuildContext context) { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text("Informații despre ${trainData.rank} ${trainData.number}"), - ), - child: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) { - final topPadding = MediaQuery.of(context).padding.top; - - return NestedScrollView( - headerSliverBuilder: (context, innerBoxIsScrolled) { - return [ - // SliverPadding( - // padding: EdgeInsets.only( - // top: topPadding, - // ), - // ), - SliverPersistentHeaderPadding(maxHeight: topPadding,) - ]; - }, - body: Builder( - builder: (context) { - return CustomScrollView( - slivers: [ - if (refresh != null) - CupertinoSliverRefreshControl( - builder: (context, mode, pulledExtent, refreshTriggerPullDistance, refreshIndicatorExtent) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: pulledExtent, - child: Column( - children: [ - Container( - height: min(refreshIndicatorExtent, pulledExtent), - child: Center( - child: Builder( - builder: (context) { - if (mode == RefreshIndicatorMode.inactive) { - return Container(); - } - else if (mode == RefreshIndicatorMode.done) { - return Text('Refreshed!'); - } - else if (mode == RefreshIndicatorMode.drag) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - CupertinoActivityIndicator(animating: false,), - Text('Pull to refresh...'), - ], - ); - } - else if (mode == RefreshIndicatorMode.armed) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - CupertinoActivityIndicator(animating: false,), - Text('Release to refresh...'), - ], - ); - } - else { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - CupertinoActivityIndicator(), - Text('Refreshing'), - ], - ); - } - }, - ), - ), - ), - Expanded(child: Container(),), - ], + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text("Informații despre ${trainData.rank} ${trainData.number}"), + trailing: refresh == null ? null : isRefreshing == true ? CupertinoActivityIndicator() : CupertinoButton( + padding: const EdgeInsets.all(0), + alignment: Alignment.center, + child: Icon(CupertinoIcons.refresh), + onPressed: () => refresh!(), + ), + ), + child: SafeArea( + top: false, + bottom: false, + child: Builder(builder: (context) { + final topPadding = MediaQuery.of(context).padding.top; + + return NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + // SliverPadding( + // padding: EdgeInsets.only( + // top: topPadding, + // ), + // ), + SliverPersistentHeaderPadding( + maxHeight: topPadding, + ) + ]; + }, + body: Builder(builder: (context) { + return CustomScrollView( + slivers: [ + if (refresh != null) + CupertinoSliverRefreshControl( + builder: (context, mode, pulledExtent, + refreshTriggerPullDistance, refreshIndicatorExtent) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: pulledExtent, + child: Column( + children: [ + Container( + height: min( + refreshIndicatorExtent, pulledExtent), + child: Center( + child: Builder( + builder: (context) { + if (mode == + RefreshIndicatorMode.inactive) { + return Container(); + } else if (mode == + RefreshIndicatorMode.done) { + return Text('Refreshed!'); + } else if (mode == + RefreshIndicatorMode.drag) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoActivityIndicator( + animating: false, + ), + Text('Pull to refresh...'), + ], + ); + } else if (mode == + RefreshIndicatorMode.armed) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoActivityIndicator( + animating: false, + ), + Text('Release to refresh...'), + ], + ); + } else { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoActivityIndicator(), + Text('Refreshing'), + ], + ); + } + }, ), ), - ], - ); - }, - onRefresh: refresh, - ), - DisplayTrainID(trainData: trainData,), - DisplayTrainOperator(trainData: trainData,), - DisplayTrainRoute(trainData: trainData,), - DisplayTrainDeparture(trainData: trainData,), - SliverToBoxAdapter( - child: CupertinoDivider( - color: FOREGROUND_WHITE, - ), - ), - DisplayTrainLastInfo(trainData: trainData,), - SliverToBoxAdapter( - child: CupertinoDivider(), - ), - SliverToBoxAdapter( - child: IntrinsicHeight( - child: Row( - children: [ - // Expanded( - // child: DisplayTrainNextStop(trainData: trainData,), - // ), - Expanded( - child: DisplayTrainDestination(trainData: trainData,), ), - SizedBox( - height: double.infinity, - child: CupertinoVerticalDivider(), + Expanded( + child: Container(), ), - Expanded(child: DisplayTrainRouteDistance(trainData: trainData,),), ], ), ), - ), - // SliverToBoxAdapter( - // child: CupertinoDivider(), - // ), - // SliverToBoxAdapter( - // child: IntrinsicHeight( - // child: Row( - // children: [ - // // Expanded( - // // child: DisplayTrainRouteDuration(trainData: trainData,), - // // ), - // Expanded(child: Container(),), - // SizedBox( - // height: double.infinity, - // child: CupertinoVerticalDivider(), - // ), - // Expanded( - // child: DisplayTrainRouteDistance(trainData: trainData,), - // ) - // ], - // ), - // ), + ], + ); + }, + onRefresh: refresh, + ), + DisplayTrainID( + trainData: trainData, + ), + DisplayTrainOperator( + trainData: trainData, + ), + DisplayTrainRoute( + trainData: trainData, + ), + DisplayTrainDeparture( + trainData: trainData, + ), + SliverToBoxAdapter( + child: CupertinoDivider( + color: FOREGROUND_WHITE, + ), + ), + DisplayTrainLastInfo( + trainData: trainData, + ), + SliverToBoxAdapter( + child: CupertinoDivider(), + ), + SliverToBoxAdapter( + child: IntrinsicHeight( + child: Row( + children: [ + // Expanded( + // child: DisplayTrainNextStop(trainData: trainData,), // ), - SliverToBoxAdapter( - child: CupertinoDivider( - color: FOREGROUND_WHITE, + Expanded( + child: DisplayTrainRouteDuration( + trainData: trainData, ), ), - DisplayTrainStations( - trainData: trainData, + // Expanded( + // child: DisplayTrainDestination(trainData: trainData,), + // ), + SizedBox( + height: double.infinity, + child: CupertinoVerticalDivider(), ), - SliverToBoxAdapter( - child: Container( - height: MediaQuery.of(context).viewPadding.bottom, + Expanded( + child: DisplayTrainRouteDistance( + trainData: trainData, ), ), ], - ); - } + ), + ), ), - ); - } - ), - ), - ); + // SliverToBoxAdapter( + // child: CupertinoDivider(), + // ), + // SliverToBoxAdapter( + // child: IntrinsicHeight( + // child: Row( + // children: [ + // // Expanded( + // // child: DisplayTrainRouteDuration(trainData: trainData,), + // // ), + // Expanded(child: Container(),), + // SizedBox( + // height: double.infinity, + // child: CupertinoVerticalDivider(), + // ), + // Expanded( + // child: DisplayTrainRouteDistance(trainData: trainData,), + // ) + // ], + // ), + // ), + // ), + SliverToBoxAdapter( + child: CupertinoDivider( + color: FOREGROUND_WHITE, + ), + ), + DisplayTrainStations( + trainData: trainData, + ), + SliverToBoxAdapter( + child: Container( + height: MediaQuery.of(context).viewPadding.bottom, + ), + ), + ], + ); + }), + ); + }), + ), + ); // return CupertinoPageScaffold( // navigationBar: CupertinoNavigationBar( @@ -343,11 +387,22 @@ class DisplayTrainID extends StatelessWidget { @override Widget build(BuildContext context) { return SliverToBoxAdapter( - child: Center( + child: Center( child: Padding( padding: const EdgeInsets.all(8.0), - child: Text( - "${trainData.rank} ${trainData.number}", + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: trainData.rank, + style: TextStyle( + color: trainData.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null, + ), + ), + TextSpan(text: ' '), + TextSpan(text: trainData.number,), + ], + ), style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle, ), ), @@ -364,31 +419,35 @@ class DisplayTrainRoute extends StatelessWidget { @override Widget build(BuildContext context) { return SliverToBoxAdapter( - child: Row( + child: Row( children: [ - Center( - child: Padding( - padding: const EdgeInsets.all(4), - child: Text( - trainData.route.from, - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 16, + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.route.from, + style: + CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), ), ), ), ), - Expanded(child: Container(),), Center(child: Text("-")), - Expanded(child: Container(),), - Center( - child: Padding( - padding: const EdgeInsets.all(4), - child: Text( - trainData.route.to, - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 16, + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.route.to, + style: + CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.right, ), - textAlign: TextAlign.right, ), ), ), @@ -410,9 +469,9 @@ class DisplayTrainOperator extends StatelessWidget { child: Text( trainData.operator, style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 14, - fontStyle: FontStyle.italic, - ), + fontSize: 14, + fontStyle: FontStyle.italic, + ), ), ), ); @@ -433,9 +492,9 @@ class DisplayTrainDeparture extends StatelessWidget { // "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}", "Plecare în ${trainData.date}", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontStyle: FontStyle.italic, - fontWeight: FontWeight.w200, - ), + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w200, + ), textAlign: TextAlign.center, ), ), @@ -451,7 +510,9 @@ class DisplayTrainLastInfo extends StatelessWidget { @override Widget build(BuildContext context) { if (trainData.status == null) { - return SliverToBoxAdapter(child: Container(),); + return SliverToBoxAdapter( + child: Container(), + ); } return SliverToBoxAdapter( @@ -464,9 +525,9 @@ class DisplayTrainLastInfo extends StatelessWidget { child: Text( "Ultima informație", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 20, - fontWeight: FontWeight.bold, - ), + fontSize: 20, + fontWeight: FontWeight.bold, + ), ), ), ), @@ -480,10 +541,12 @@ class DisplayTrainLastInfo extends StatelessWidget { textAlign: TextAlign.left, ), ), - Expanded(child: Container(),), + Expanded( + child: Container(), + ), Padding( padding: const EdgeInsets.all(4), - child: Text( + child: Text( stateToString(trainData.status!.state), style: CupertinoTheme.of(context).textTheme.textStyle, textAlign: TextAlign.right, @@ -510,20 +573,21 @@ class DisplayTrainLastInfo extends StatelessWidget { if (data > 0) { return Text( - "$data minute întârziere", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 14, - color: CupertinoColors.destructiveRed, - ), + "$data ${data == 1 ? 'minut' : 'minute'} întârziere", + style: + CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + color: CupertinoColors.destructiveRed, + ), ); - } - else { + } else { return Text( - "${-data} minute mai devreme", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 12, - color: CupertinoColors.activeGreen, - ), + "${-data} ${data == -1 ? 'minut' : 'minute'} mai devreme", + style: + CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 12, + color: CupertinoColors.activeGreen, + ), ); } }, @@ -545,7 +609,7 @@ class DisplayTrainLastInfo extends StatelessWidget { // future: trainData.nextStop.stationName, // builder: (context, snapshot) { // if (!snapshot.hasData) return Container(); -// +// // return Column( // mainAxisSize: MainAxisSize.min, // children: [ @@ -627,9 +691,9 @@ class DisplayTrainDestination extends StatelessWidget { child: Text( "Destinația", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 20, - fontWeight: FontWeight.bold, - ), + fontSize: 20, + fontWeight: FontWeight.bold, + ), textAlign: TextAlign.center, ), ), @@ -641,20 +705,22 @@ class DisplayTrainDestination extends StatelessWidget { child: Text( trainData.stations.last.name, style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 18, - fontWeight: FontWeight.w500, - ), + fontSize: 18, + fontWeight: FontWeight.w500, + ), textAlign: TextAlign.center, ), ), Builder( builder: (context) { - final arrival = trainData.stations.last.arrival!.scheduleTime; + final arrival = + DateTime.parse(trainData.stations.last.arrival!.scheduleTime); final delay = trainData.stations.last.arrival!.status?.delay ?? 0; - final parts = arrival.split(':'); - final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1])); - final arrivalWithDelay = arrivalDT.add(Duration(minutes: delay)); - final arrivalWithDelayString = '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}'; + // final parts = arrival.split(':'); + // final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1])); + final arrivalWithDelay = arrival.add(Duration(minutes: delay)); + final arrivalWithDelayString = + '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}'; // const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; return Column( @@ -673,26 +739,32 @@ class DisplayTrainDestination extends StatelessWidget { children: [ TextSpan(text: ' '), TextSpan( - text: '$arrival', - style: delay == 0 ? null : TextStyle( - decoration: TextDecoration.lineThrough, - ), + text: + '${arrival.hour.toString().padLeft(2, "0")}:${arrival.minute.toString().padLeft(2, "0")}', + style: delay == 0 + ? null + : TextStyle( + decoration: TextDecoration.lineThrough, + ), ), if (delay != 0) ...[ TextSpan(text: ' '), TextSpan( text: '$arrivalWithDelayString', style: TextStyle( - color: delay > 0 ? CupertinoColors.destructiveRed : CupertinoColors.activeGreen, + color: delay > 0 + ? CupertinoColors.destructiveRed + : CupertinoColors.activeGreen, ), ), ] ], ), // "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 14, - ), + style: + CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + ), textAlign: TextAlign.center, ), ], @@ -717,16 +789,16 @@ class DisplayTrainRouteDistance extends StatelessWidget { Text( "Distanța rutei", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + fontSize: 18, + fontWeight: FontWeight.bold, + ), textAlign: TextAlign.center, ), Text( "${trainData.stations.last.km} km", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 16, - ), + fontSize: 16, + ), textAlign: TextAlign.center, ), ], @@ -734,76 +806,87 @@ class DisplayTrainRouteDistance extends StatelessWidget { } } -// class DisplayTrainRouteDuration extends StatelessWidget { -// final TrainData trainData; -// -// DisplayTrainRouteDuration({required this.trainData}); -// -// @override -// Widget build(BuildContext context) { -// return Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// Text( -// "Durata rutei", -// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( -// fontSize: 18, -// fontWeight: FontWeight.bold, -// ), -// textAlign: TextAlign.center, -// ), -// FutureDisplay( -// future: trainData.routeDuration, -// builder: (context, duration) { -// var durationString = StringBuffer(); -// -// bool firstWritten = false; -// -// if (duration.inDays > 0) { -// firstWritten = true; -// if (duration.inDays == 1) durationString.write("1 zi"); -// else durationString.write("${duration.inDays} zile"); -// duration -= Duration(days: duration.inDays); -// } -// -// if (duration.inHours > 0) { -// if (firstWritten) { -// durationString.write(", "); -// } -// firstWritten = true; -// if (duration.inHours == 1) durationString.write("1 oră"); -// else durationString.write("${duration.inHours} ore"); -// duration -= Duration(hours: duration.inHours); -// } -// -// if (duration.inMinutes > 0) { -// if (firstWritten) { -// durationString.write(", "); -// } -// firstWritten = true; -// if (duration.inMinutes == 1) durationString.write("1 minut"); -// else durationString.write("${duration.inMinutes} minute"); -// duration -= Duration(minutes: duration.inMinutes); -// } -// -// return Text( -// durationString.toString(), -// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( -// fontSize: 16, -// ), -// textAlign: TextAlign.center, -// ); -// }, -// ), -// ], -// ); -// } -// } +class DisplayTrainRouteDuration extends StatelessWidget { + final TrainData trainData; + + DisplayTrainRouteDuration({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Durata rutei", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + Builder( + builder: (context) { + var duration = + DateTime.parse(trainData.stations.last.arrival!.scheduleTime) + .difference(DateTime.parse( + trainData.stations.first.departure!.scheduleTime)); + var durationString = StringBuffer(); + + bool firstWritten = false; + + if (duration.inDays > 0) { + firstWritten = true; + if (duration.inDays == 1) + durationString.write("1 zi"); + else + durationString.write("${duration.inDays} zile"); + duration -= Duration(days: duration.inDays); + } + + if (duration.inHours > 0) { + if (firstWritten) { + durationString.write(", "); + } + firstWritten = true; + if (duration.inHours == 1) + durationString.write("1 oră"); + else + durationString.write("${duration.inHours} ore"); + duration -= Duration(hours: duration.inHours); + } + + if (duration.inMinutes > 0) { + if (firstWritten) { + durationString.write(", "); + } + firstWritten = true; + if (duration.inMinutes == 1) + durationString.write("1 minut"); + else + durationString.write("${duration.inMinutes} minute"); + duration -= Duration(minutes: duration.inMinutes); + } + + return Text( + durationString.toString(), + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.center, + ); + }, + ), + ], + ); + } +} class DisplayTrainStations extends StatelessWidget { final TrainData trainData; - DisplayTrainStations({required this.trainData,}); + DisplayTrainStations({ + required this.trainData, + }); @override Widget build(BuildContext context) { @@ -812,8 +895,7 @@ class DisplayTrainStations extends StatelessWidget { (context, index) { if (index.isOdd) { return CupertinoDivider(); - } - else { + } else { final itemIndex = index ~/ 2; return IndexedSemantics( child: DisplayTrainStation( diff --git a/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart index 85a8eb0..6aa98e6 100644 --- a/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart +++ b/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart @@ -43,8 +43,9 @@ class DisplayTrainStation extends StatelessWidget { final isOnTime = delay <= 0 && real == true; final isNotScheduled = false; - return KmBadge( - station: station, + return Badge( + text: station.km.toString(), + caption: 'km', isNotScheduled: isNotScheduled, isDelayed: isDelayed, isOnTime: isOnTime, @@ -55,7 +56,11 @@ class DisplayTrainStation extends StatelessWidget { child: Title( station: station, ), - ) + ), + if (station.platform == null) + Container(width: 48, height: 48,) + else + Badge(text: station.platform!, caption: 'linia'), ], ), Time( @@ -69,14 +74,16 @@ class DisplayTrainStation extends StatelessWidget { } } -class KmBadge extends StatelessWidget { - final Station station; +class Badge extends StatelessWidget { + final String text; + final String caption; final bool isNotScheduled; final bool isOnTime; final bool isDelayed; - KmBadge({ - required this.station, + Badge({ + required this.text, + required this.caption, this.isNotScheduled = false, this.isOnTime = false, this.isDelayed = false, @@ -120,7 +127,7 @@ class KmBadge extends StatelessWidget { Expanded( child: Center( child: Text( - station.km.toString(), + text, style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( fontSize: 20, fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, @@ -131,9 +138,9 @@ class KmBadge extends StatelessWidget { ), ), Text( - "km", + caption, style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 10, + fontSize: 12, color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, ), ), @@ -247,16 +254,13 @@ class ArrivalTime extends StatelessWidget { } else { final delay = station.arrival!.status?.delay ?? 0; - final time = station.arrival!.scheduleTime; + final time = DateTime.parse(station.arrival!.scheduleTime); if (delay == 0) { - return Text("$time"); + return Text("${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}"); } else if (delay > 0) { - final splits = time.split(":").map((s) => int.parse(s)).toList(); - - final now = DateTime.now(); - final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final oldDate = time; final newDate = oldDate.add(Duration(minutes: delay)); return Column( @@ -278,10 +282,7 @@ class ArrivalTime extends StatelessWidget { ); } else { - final splits = time.split(":").map((s) => int.parse(s)).toList(); - - final now = DateTime.now(); - final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final oldDate = time; final newDate = oldDate.add(Duration(minutes: delay)); return Column( @@ -326,21 +327,26 @@ class StopTime extends StatelessWidget { Builder( builder: (context) { int stopsForInt = stopsFor; + bool minutes = false; + if (stopsForInt >= 60) { + stopsForInt ~/= 60; + minutes = true; + } if (stopsForInt == 1) { return Text( - "1 minut", + minutes ? '1 minut' : '1 secundă', textAlign: TextAlign.center, ); } else if (stopsForInt < 20) { return Text( - "$stopsFor minute", + '$stopsForInt ' + (minutes ? 'minute' : 'seconde'), textAlign: TextAlign.center, ); } else { return Text( - "$stopsFor de minute", + '$stopsForInt de ' + (minutes ? 'minute' : 'secunde'), textAlign: TextAlign.center, ); } @@ -381,16 +387,13 @@ class DepartureTime extends StatelessWidget { } else { final delay = station.departure!.status?.delay ?? 0; - final time = station.departure!.scheduleTime; + final time = DateTime.parse(station.departure!.scheduleTime); if (delay == 0) { - return Text("$time"); + return Text("${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}"); } else if (delay > 0) { - final splits = time.split(":").map((s) => int.parse(s)).toList(); - - final now = DateTime.now(); - final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final oldDate = time; final newDate = oldDate.add(Duration(minutes: delay)); return Column( @@ -412,10 +415,7 @@ class DepartureTime extends StatelessWidget { ); } else { - final splits = time.split(":").map((s) => int.parse(s)).toList(); - - final now = DateTime.now(); - final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final oldDate = time; final newDate = oldDate.add(Duration(minutes: delay)); return Column( @@ -460,7 +460,7 @@ class Delay extends StatelessWidget { if (delay == 0 || delay == null) return Container(); else if (delay > 0) { return Text( - "$delay minute întârziere", + "$delay ${delay == 1 ? 'minut' : 'minute'} întârziere", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( color: CupertinoColors.destructiveRed, fontSize: 14, @@ -470,7 +470,7 @@ class Delay extends StatelessWidget { } else if (delay < 0) { return Text( - "${-delay} minute mai devreme", + "${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( color: CupertinoColors.activeGreen, fontSize: 14, diff --git a/lib/pages/train_info_page/view_train/train_info_material.dart b/lib/pages/train_info_page/view_train/train_info_material.dart index 3d0907c..e4840b3 100644 --- a/lib/pages/train_info_page/view_train/train_info_material.dart +++ b/lib/pages/train_info_page/view_train/train_info_material.dart @@ -75,12 +75,18 @@ class TrainInfoMaterial extends StatelessWidget { body: Column( children: [ if (isSmallScreen(context)) - SlimAppBar( - title: 'INFO TREN - ${trainData.rank} ${trainData.number}' + SafeArea( + bottom: false, + left: false, + right: false, + child: SlimAppBar( + title: 'INFO TREN - ${trainData.rank} ${trainData.number}' + ), ), Expanded( child: SafeArea( bottom: false, + top: isSmallScreen(context) ? false : true, child: RefreshIndicator( onRefresh: refresh ?? () async {}, child: CustomScrollView( @@ -114,7 +120,8 @@ class TrainInfoMaterial extends StatelessWidget { child: Row( children: [ // Expanded(child: DisplayTrainNextStop(trainData: trainData,)), - Expanded(child: DisplayTrainDestination(trainData: trainData,)), + // Expanded(child: DisplayTrainDestination(trainData: trainData,)), + Expanded(child: DisplayTrainRouteDuration(trainData: trainData,)), Expanded(child: DisplayTrainRouteDistance(trainData: trainData,),), ], ), @@ -165,8 +172,19 @@ class DisplayTrainID extends StatelessWidget { @override Widget build(BuildContext context) { - return Text( - "${trainData.rank} ${trainData.number}", + return Text.rich( + TextSpan( + children: [ + TextSpan( + text: trainData.rank, + style: TextStyle( + color: trainData.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null, + ), + ), + TextSpan(text: ' '), + TextSpan(text: trainData.number,), + ], + ), style: (isSmallScreen(context) ? Theme.of(context).textTheme.headline4 : Theme.of(context).textTheme.headline3)?.copyWith( @@ -205,29 +223,31 @@ class DisplayTrainRoute extends StatelessWidget { Widget build(BuildContext context) { return Row( children: [ - Center( - child: Padding( - padding: const EdgeInsets.all(4), - child: Text( - trainData.route.from, - style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: 16, + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.route.from, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: 16, + ), ), ), ), ), - Expanded(child: Container(),), Center(child: Text("-")), - Expanded(child: Container(),), - Center( - child: Padding( - padding: const EdgeInsets.all(4), - child: Text( - trainData.route.to, - style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: 16, + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.route.to, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.right, ), - textAlign: TextAlign.right, ), ), ), @@ -336,7 +356,7 @@ class DisplayTrainLastInfo extends StatelessWidget { if (data > 0) { return Text( - "$data minute întârziere", + "$data ${data == 1 ? 'minut' : 'minute'} întârziere", style: Theme.of(context).textTheme.bodyText2?.copyWith( fontSize: isSmallScreen(context) ? 14 : 16, color: Colors.red.shade300, @@ -345,7 +365,7 @@ class DisplayTrainLastInfo extends StatelessWidget { } else { return Text( - "${-data} minute mai devreme", + "${-data} ${data == -1 ? 'minut' : 'minute'} mai devreme", style: Theme.of(context).textTheme.bodyText2?.copyWith( fontSize: isSmallScreen(context) ? 14 : 16, color: Colors.green.shade300, @@ -486,11 +506,9 @@ class DisplayTrainDestination extends StatelessWidget { ), Builder( builder: (context) { - final arrival = destination.arrival!.scheduleTime; + final arrival = DateTime.parse(destination.arrival!.scheduleTime); final delay = trainData.stations.last.arrival!.status?.delay ?? 0; - final parts = arrival.split(':'); - final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1])); - final arrivalWithDelay = arrivalDT.add(Duration(minutes: delay)); + final arrivalWithDelay = arrival.add(Duration(minutes: delay)); final arrivalWithDelayString = '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}'; // const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; @@ -511,7 +529,7 @@ class DisplayTrainDestination extends StatelessWidget { children: [ TextSpan(text: ' '), TextSpan( - text: '$arrival', + text: '${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}', style: delay == 0 ? null : TextStyle( decoration: TextDecoration.lineThrough, ), @@ -581,82 +599,81 @@ class DisplayTrainRouteDistance extends StatelessWidget { } } -// class DisplayTrainRouteDuration extends StatelessWidget { -// final TrainData trainData; -// -// DisplayTrainRouteDuration({required this.trainData}); -// -// @override -// Widget build(BuildContext context) { -// return Card( -// child: Center( -// child: Padding( -// padding: const EdgeInsets.all(2), -// child: Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// Text( -// "Durata rutei", -// style: Theme.of(context).textTheme.bodyText2?.copyWith( -// fontSize: isSmallScreen(context) ? 16 : 18, -// fontWeight: FontWeight.bold, -// ), -// textAlign: TextAlign.center, -// ), -// FutureDisplay( -// future: trainData.routeDuration, -// builder: (context, duration) { -// var durationString = StringBuffer(); -// -// bool firstWritten = false; -// -// if (duration.inDays > 0) { -// firstWritten = true; -// if (duration.inDays == 1) durationString.write("1 zi"); -// else durationString.write("${duration.inDays} zile"); -// duration -= Duration(days: duration.inDays); -// } -// -// if (duration.inHours > 0) { -// if (firstWritten) { -// durationString.write(", "); -// } -// firstWritten = true; -// if (duration.inHours == 1) durationString.write("1 oră"); -// else durationString.write("${duration.inHours} ore"); -// duration -= Duration(hours: duration.inHours); -// } -// -// if (duration.inMinutes > 0) { -// if (firstWritten) { -// durationString.write(", "); -// } -// firstWritten = true; -// if (duration.inMinutes == 1) durationString.write("1 minut"); -// else durationString.write("${duration.inMinutes} minute"); -// duration -= Duration(minutes: duration.inMinutes); -// } -// -// return Text( -// durationString.toString(), -// style: Theme.of(context).textTheme.bodyText2?.copyWith( -// fontSize: isSmallScreen(context) ? 14 : 16, -// ), -// textAlign: TextAlign.center, -// ); -// }, -// ), -// ], -// ), -// ), -// ), -// ); -// } -// } +class DisplayTrainRouteDuration extends StatelessWidget { + final TrainData trainData; + + DisplayTrainRouteDuration({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Card( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Durata rutei", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 20 : 22, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + Builder( + builder: (context) { + var duration = DateTime.parse(trainData.stations.last.arrival!.scheduleTime).difference(DateTime.parse(trainData.stations.first.departure!.scheduleTime)); + var durationString = StringBuffer(); + + bool firstWritten = false; + + if (duration.inDays > 0) { + firstWritten = true; + if (duration.inDays == 1) durationString.write("1 zi"); + else durationString.write("${duration.inDays} zile"); + duration -= Duration(days: duration.inDays); + } + + if (duration.inHours > 0) { + if (firstWritten) { + durationString.write(", "); + } + firstWritten = true; + if (duration.inHours == 1) durationString.write("1 oră"); + else durationString.write("${duration.inHours} ore"); + duration -= Duration(hours: duration.inHours); + } + + if (duration.inMinutes > 0) { + if (firstWritten) { + durationString.write(", "); + } + firstWritten = true; + if (duration.inMinutes == 1) durationString.write("1 minut"); + else durationString.write("${duration.inMinutes} minute"); + duration -= Duration(minutes: duration.inMinutes); + } + + return Text( + durationString.toString(), + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 18 : 20, + ), + textAlign: TextAlign.center, + ); + }, + ), + ], + ), + ), + ), + ); + } +} class DisplayTrainStations extends StatelessWidget { final TrainData trainData; - DisplayTrainStations({required this.trainData}); @override diff --git a/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart index 6802bbf..d1ce1e7 100644 --- a/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart +++ b/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart @@ -46,8 +46,9 @@ class DisplayTrainStation extends StatelessWidget { final isOnTime = delay <= 0 && real == true; final isNotScheduled = false; - return KmBadge( - station: station, + return Badge( + text: station.km.toString(), + caption: 'km', isNotScheduled: isNotScheduled, isDelayed: isDelayed, isOnTime: isOnTime, @@ -59,6 +60,10 @@ class DisplayTrainStation extends StatelessWidget { station: station, ), ), + if (station.platform == null) + Container(width: isSmallScreen(context) ? 42 : 48, height: isSmallScreen(context) ? 42 : 48,) + else + Badge(text: station.platform!, caption: 'linia',), ], ), Time( @@ -74,14 +79,16 @@ class DisplayTrainStation extends StatelessWidget { } } -class KmBadge extends StatelessWidget { - final Station station; +class Badge extends StatelessWidget { + final String text; + final String caption; final bool isNotScheduled; final bool isOnTime; final bool isDelayed; - KmBadge({ - required this.station, + Badge({ + required this.text, + required this.caption, this.isNotScheduled = false, this.isOnTime = false, this.isDelayed = false, @@ -124,7 +131,7 @@ class KmBadge extends StatelessWidget { Expanded( child: Center( child: Text( - station.km.toString(), + text, style: Theme.of(context).textTheme.bodyText2?.copyWith( fontSize: isSmallScreen(context) ? 16 : 20, fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, @@ -135,7 +142,7 @@ class KmBadge extends StatelessWidget { ), ), Text( - "km", + caption, style: Theme.of(context).textTheme.bodyText2?.copyWith( fontSize: 10, color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, @@ -254,16 +261,13 @@ class ArrivalTime extends StatelessWidget { } else { final delay = station.arrival!.status?.delay ?? 0; - final time = station.arrival!.scheduleTime; + final time = DateTime.parse(station.arrival!.scheduleTime); if (delay == 0) { - return Text("$time"); + return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); } else if (delay > 0) { - final splits = time.split(":").map((s) => int.parse(s)).toList(); - - final now = DateTime.now(); - final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final oldDate = time; final newDate = oldDate.add(Duration(minutes: delay)); return Column( @@ -285,10 +289,7 @@ class ArrivalTime extends StatelessWidget { ); } else { - final splits = time.split(":").map((s) => int.parse(s)).toList(); - - final now = DateTime.now(); - final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final oldDate = time; final newDate = oldDate.add(Duration(minutes: delay)); return Column( @@ -332,21 +333,26 @@ class StopTime extends StatelessWidget { Builder( builder: (context) { int stopsForInt = station.stoppingTime!; + bool minutes = false; + if (stopsForInt >= 60) { + stopsForInt ~/= 60; + minutes = true; + } if (stopsForInt == 1) { return Text( - "1 minut", + "1 " + (minutes ? 'minut' : 'secundă'), textAlign: TextAlign.center, ); } else if (stopsForInt < 20) { return Text( - "${station.stoppingTime} minute", + "$stopsForInt ${minutes ? 'minute' : 'secunde'}", textAlign: TextAlign.center, ); } else { return Text( - "${station.stoppingTime} de minute", + "$stopsForInt de ${minutes ? 'minute' : 'secunde'}", textAlign: TextAlign.center, ); } @@ -390,16 +396,13 @@ class DepartureTime extends StatelessWidget { } else { final delay = station.departure!.status?.delay ?? 0; - final time = station.departure!.scheduleTime; + final time = DateTime.parse(station.departure!.scheduleTime); if (delay == 0) { - return Text("$time"); + return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); } else if (delay > 0) { - final splits = time.split(":").map((s) => int.parse(s)).toList(); - - final now = DateTime.now(); - final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final oldDate = time; final newDate = oldDate.add(Duration(minutes: delay)); return Column( @@ -421,10 +424,7 @@ class DepartureTime extends StatelessWidget { ); } else { - final splits = time.split(":").map((s) => int.parse(s)).toList(); - - final now = DateTime.now(); - final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final oldDate = time; final newDate = oldDate.add(Duration(minutes: delay)); return Column( @@ -470,7 +470,7 @@ class Delay extends StatelessWidget { if (delay == 0 || delay == null) return Container(); else if (delay > 0) { return Text( - "$delay minute întârziere", + "$delay ${delay == 1 ? 'minut' : 'minute'} întârziere", style: Theme.of(context).textTheme.bodyText2?.copyWith( color: Colors.red.shade300, fontSize: 14, @@ -480,7 +480,7 @@ class Delay extends StatelessWidget { } else if (delay < 0) { return Text( - "${-delay} minute mai devreme", + "${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme", style: Theme.of(context).textTheme.bodyText2?.copyWith( color: Colors.green.shade300, fontSize: 14, diff --git a/pubspec.yaml b/pubspec.yaml index 1855cce..c4c7a5d 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.3.1 +version: 2.4.0 environment: sdk: ">=2.12.0 <3.0.0"