From 7fcbdc18bd4dd682db82d405acd389e1fcca5285 Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Thu, 4 Aug 2022 17:52:06 +0300 Subject: [PATCH] Transition to Material 3, main page redesign --- CHANGELOG.txt | 7 + lib/components/badge.dart | 157 ++++++++++ lib/main.dart | 1 + lib/pages/about/about_page_cupertino.dart | 5 +- lib/pages/about/about_page_material.dart | 33 ++- lib/pages/main/main_page.dart | 6 +- lib/pages/main/main_page_material.dart | 27 +- .../view_station/view_station.dart | 2 + .../view_station/view_station_material.dart | 32 ++- ...in_info_cupertino_DisplayTrainStation.dart | 83 +----- .../view_train/train_info_material.dart | 268 +++++++++++------- ...ain_info_material_DisplayTrainStation.dart | 215 +++++--------- pubspec.yaml | 2 +- 13 files changed, 491 insertions(+), 347 deletions(-) create mode 100644 lib/components/badge.dart diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9653a13..e866b45 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,10 @@ +v2.7.6 +Transitioned to Material 3. +Redesigned main page on Material. +On Android (Material), tapping station card in train information screen opens departures/arrivals board. +Added past tense to trains already arrived/departed. +Fixed download button on Android. + v2.7.5 Added about page and in-app changelog. On Android, added download buttons. diff --git a/lib/components/badge.dart b/lib/components/badge.dart new file mode 100644 index 0000000..644b58f --- /dev/null +++ b/lib/components/badge.dart @@ -0,0 +1,157 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; + +class MaterialBadge extends StatelessWidget { + final String text; + final String caption; + final bool isNotScheduled; + final bool isOnTime; + final bool isDelayed; + + MaterialBadge({ + required this.text, + required this.caption, + this.isNotScheduled = false, + this.isOnTime = false, + this.isDelayed = false, + }); + + @override + Widget build(BuildContext context) { + Color foregroundColor = Colors.white70; + Color? backgroundColor; + + if (isNotScheduled) { + foregroundColor = Colors.orange.shade300; + backgroundColor = Colors.orange.shade900.withOpacity(0.3); + } + else if (isOnTime) { + foregroundColor = Colors.green.shade300; + backgroundColor = Colors.green.shade900.withOpacity(0.3); + } + else if (isDelayed) { + foregroundColor = Colors.red.shade300; + backgroundColor = Colors.red.shade900.withOpacity(0.3); + } + + return Padding( + padding: const EdgeInsets.all(8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + width: 2, + color: foregroundColor, + ), + color: backgroundColor, + ), + width: isSmallScreen(context) ? 42 : 48, + height: isSmallScreen(context) ? 42 : 48, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Center( + child: Text( + text, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 16 : 20, + fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, + color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, + ), + textAlign: TextAlign.center, + ), + ), + ), + Text( + caption, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: 10, + color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, + ), + ), + ], + ), + ), + ); + } +} + +class CupertinoBadge extends StatelessWidget { + final String text; + final String caption; + final bool isNotScheduled; + final bool isOnTime; + final bool isDelayed; + + CupertinoBadge({ + required this.text, + required this.caption, + this.isNotScheduled = false, + this.isOnTime = false, + this.isDelayed = false, + }); + + @override + Widget build(BuildContext context) { + Color foregroundColor = FOREGROUND_WHITE; + Color? backgroundColor; + + if (isNotScheduled) { + foregroundColor = Color.fromRGBO(225, 175, 30, 1); + backgroundColor = Color.fromRGBO(80, 40, 10, 1); + } + else if (isOnTime) { + foregroundColor = Color.fromRGBO(130, 175, 65, 1); + backgroundColor = Color.fromRGBO(40, 80, 10, 1); + } + else if (isDelayed) { + foregroundColor = Color.fromRGBO(225, 75, 30, 1); + backgroundColor = Color.fromRGBO(80, 20, 10, 1); + } + + return Padding( + padding: const EdgeInsets.all(8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + width: 2, + color: foregroundColor, + ), + color: backgroundColor, + // color: CupertinoColors.activeOrange, + ), + width: 48, + height: 48, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Center( + child: Text( + text, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 20, + fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, + color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, + ), + textAlign: TextAlign.center, + ), + ), + ), + Text( + caption, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 12, + color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index fcd0b0a..58d74dc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -97,6 +97,7 @@ class StartPoint extends StatelessWidget { primarySwatch: Colors.blue, accentColor: Colors.blue.shade700, ), + useMaterial3: true, // fontFamily: 'Atkinson Hyperlegible', ), routes: routesByUiDesign(UiDesign.MATERIAL), diff --git a/lib/pages/about/about_page_cupertino.dart b/lib/pages/about/about_page_cupertino.dart index 72306a1..88201d9 100644 --- a/lib/pages/about/about_page_cupertino.dart +++ b/lib/pages/about/about_page_cupertino.dart @@ -100,7 +100,10 @@ class AboutPageStateCupertino extends AboutPageState { padding: EdgeInsets.all(4), minSize: 0, onPressed: () { - launchUrl(log.apkLink!); + launchUrl( + log.apkLink!, + mode: LaunchMode.externalApplication, + ); }, child: Icon(CupertinoIcons.arrow_down_circle), ), diff --git a/lib/pages/about/about_page_material.dart b/lib/pages/about/about_page_material.dart index aff2a66..9cba0a5 100644 --- a/lib/pages/about/about_page_material.dart +++ b/lib/pages/about/about_page_material.dart @@ -1,4 +1,6 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:info_tren/pages/about/about_page.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -85,12 +87,33 @@ class AboutPageStateMaterial extends AboutPageState { ), ), if (AboutPageState.DOWNLOAD == 'apk' && log.apkLink != null) - IconButton( - onPressed: () { - launchUrl(log.apkLink!); + GestureDetector( + onSecondaryTap: () { + Clipboard.setData(ClipboardData(text: log.apkLink!.toString())); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Link copied to clipboard'), + )); + }, + onLongPress: () { + Clipboard.setData(ClipboardData(text: log.apkLink!.toString())); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Link copied to clipboard'), + )); + }, + onTap: () { + launchUrl( + log.apkLink!, + mode: LaunchMode.externalApplication, + ); }, - icon: Icon(Icons.download), - tooltip: 'Download APK', + behavior: HitTestBehavior.translucent, + child: Tooltip( + message: 'Download APK', + child: Padding( + padding: const EdgeInsets.all(4), + child: Icon(Icons.download), + ), + ), ), ], ), diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart index e285d6b..a129e12 100644 --- a/lib/pages/main/main_page.dart +++ b/lib/pages/main/main_page.dart @@ -43,18 +43,21 @@ abstract class MainPageShared extends StatelessWidget { List get options => [ MainPageAction( name: 'Informații despre tren', + description: 'Află informații despre parcursul unui anumit tren', action: (context) { onTrainInfoPageInvoke(context); }, ), MainPageAction( name: 'Tabelă plecari/sosiri', + description: 'Vezi trenurile care pleacă și sosesc dintr-o gară', action: (context) { onStationBoardPageInvoke(context); }, ), MainPageAction( name: 'Planificare rută', + description: 'Găsește trenurile disponibile pentru călătoria între două gări', // TODO: Implement route planning action: null, ), @@ -75,7 +78,8 @@ abstract class MainPageShared extends StatelessWidget { class MainPageAction { final String name; + final String? description; final void Function(BuildContext context)? action; - MainPageAction({required this.name, this.action}); + MainPageAction({required this.name, this.action, this.description}); } diff --git a/lib/pages/main/main_page_material.dart b/lib/pages/main/main_page_material.dart index e4e43c1..9ff1609 100644 --- a/lib/pages/main/main_page_material.dart +++ b/lib/pages/main/main_page_material.dart @@ -26,12 +26,29 @@ class MainPageMaterial extends MainPageShared { child: Center( child: Column( mainAxisSize: MainAxisSize.min, - children: options.map((option) => ElevatedButton( - child: Text( - option.name, - style: Theme.of(context).textTheme.button?.copyWith(fontSize: 18), + children: options.map((option) => Card( + color: option.action != null ? Theme.of(context).colorScheme.secondaryContainer : null, + child: InkWell( + onTap: option.action != null ? () => option.action!(context) : null, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + option.name, + style: Theme.of(context).textTheme.headline4?.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text(option.description!), + ), + ], + ), ), - onPressed: option.action != null ? () => option.action!(context) : null, )).map((w) => Padding( padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), child: SizedBox( 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 90c02d9..a8b2156 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station.dart +++ b/lib/pages/station_arrdep_page/view_station/view_station.dart @@ -33,7 +33,9 @@ abstract class ViewStationPageState extends State { static const departures = 'Pleacări'; static const loadingText = 'Se încarcă...'; static const arrivesFrom = 'Sosește de la'; + static const arrivedFrom = 'A sosit de la'; static const departsTo = 'Pleacă către'; + static const departedTo = 'A plecat către'; ViewStationPageTab tab = ViewStationPageTab.departures; late String stationName; 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 93aff9f..cf699eb 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 @@ -13,19 +13,23 @@ class ViewStationPageStateMaterial extends ViewStationPageState { title: Text(snapshot.hasData ? snapshot.data!.stationName : stationName), centerTitle: true, ), - body: snapshot.state == RefreshFutureBuilderState.waiting ? Loading(text: ViewStationPageState.loadingText, uiDesign: widget.uiDesign,) : CustomScrollView( - slivers: [ - SliverToBoxAdapter(child: SafeArea(child: Container(), left: false, bottom: false, right: false,),), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - return tab == ViewStationPageTab.arrivals ? buildStationArrivalItem(context, snapshot.data!.arrivals![index]) : buildStationDepartureItem(context, snapshot.data!.departures![index]); - }, - childCount: tab == ViewStationPageTab.arrivals ? snapshot.data!.arrivals?.length ?? 0 : snapshot.data!.departures?.length ?? 0, + body: snapshot.state == RefreshFutureBuilderState.waiting + ? Loading(text: ViewStationPageState.loadingText, uiDesign: widget.uiDesign,) + : snapshot.state == RefreshFutureBuilderState.error + ? Container() + : CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: SafeArea(child: Container(), left: false, bottom: false, right: false,),), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return tab == ViewStationPageTab.arrivals ? buildStationArrivalItem(context, snapshot.data!.arrivals![index]) : buildStationDepartureItem(context, snapshot.data!.departures![index]); + }, + childCount: tab == ViewStationPageTab.arrivals ? snapshot.data!.arrivals?.length ?? 0 : snapshot.data!.departures?.length ?? 0, + ), ), - ), - ], - ), + ], + ), bottomNavigationBar: snapshot.hasData ? BottomNavigationBar( items: [ BottomNavigationBarItem( @@ -64,7 +68,7 @@ class ViewStationPageStateMaterial extends ViewStationPageState { subtitle: Text.rich( TextSpan( children: [ - TextSpan(text: ViewStationPageState.arrivesFrom), + TextSpan(text: item.time.compareTo(DateTime.now()) < 0 ? ViewStationPageState.arrivedFrom : ViewStationPageState.arrivesFrom), TextSpan(text: ' '), TextSpan(text: item.train.origin), ], @@ -95,7 +99,7 @@ class ViewStationPageStateMaterial extends ViewStationPageState { subtitle: Text.rich( TextSpan( children: [ - TextSpan(text: ViewStationPageState.departsTo), + TextSpan(text: item.time.compareTo(DateTime.now()) < 0 ? ViewStationPageState.departedTo : ViewStationPageState.departsTo), TextSpan(text: ' '), TextSpan(text: item.train.destination), ], 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 58ee11b..46a37d0 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 @@ -1,6 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:info_tren/models/train_data.dart'; -import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; +import 'package:info_tren/components/badge.dart'; class DisplayTrainStation extends StatelessWidget { final Station station; @@ -47,7 +47,7 @@ class DisplayTrainStation extends StatelessWidget { final isOnTime = delay <= 0 && real == true; final isNotScheduled = false; - return Badge( + return CupertinoBadge( text: station.km.toString(), caption: 'km', isNotScheduled: isNotScheduled, @@ -65,7 +65,7 @@ class DisplayTrainStation extends StatelessWidget { flex: 1, child: Align( alignment: Alignment.centerRight, - child: station.platform == null ? Container() : Badge(text: station.platform!, caption: 'linia'), + child: station.platform == null ? Container() : CupertinoBadge(text: station.platform!, caption: 'linia'), ), ), ], @@ -81,83 +81,6 @@ class DisplayTrainStation extends StatelessWidget { } } -class Badge extends StatelessWidget { - final String text; - final String caption; - final bool isNotScheduled; - final bool isOnTime; - final bool isDelayed; - - Badge({ - required this.text, - required this.caption, - this.isNotScheduled = false, - this.isOnTime = false, - this.isDelayed = false, - }); - - @override - Widget build(BuildContext context) { - Color foregroundColor = FOREGROUND_WHITE; - Color? backgroundColor; - - if (isNotScheduled) { - foregroundColor = Color.fromRGBO(225, 175, 30, 1); - backgroundColor = Color.fromRGBO(80, 40, 10, 1); - } - else if (isOnTime) { - foregroundColor = Color.fromRGBO(130, 175, 65, 1); - backgroundColor = Color.fromRGBO(40, 80, 10, 1); - } - else if (isDelayed) { - foregroundColor = Color.fromRGBO(225, 75, 30, 1); - backgroundColor = Color.fromRGBO(80, 20, 10, 1); - } - - return Padding( - padding: const EdgeInsets.all(8), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - width: 2, - color: foregroundColor, - ), - color: backgroundColor, - // color: CupertinoColors.activeOrange, - ), - width: 48, - height: 48, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Center( - child: Text( - text, - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 20, - fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, - color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, - ), - textAlign: TextAlign.center, - ), - ), - ), - Text( - caption, - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 12, - color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, - ), - ), - ], - ), - ), - ); - } -} - class Title extends StatelessWidget { final Station station; 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 904ff19..0bb41f8 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 @@ -3,18 +3,24 @@ import 'package:flutter/material.dart'; import 'package:info_tren/components/slim_app_bar.dart'; import 'package:info_tren/models/train_data.dart' hide State; import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart'; import 'package:info_tren/utils/state_to_string.dart'; class TrainInfoLoadingMaterial extends TrainInfoLoading { - TrainInfoLoadingMaterial({required String title, String? loadingText}) : super(title: title, loadingText: loadingText, uiDesign: UiDesign.MATERIAL); + TrainInfoLoadingMaterial({required String title, String? loadingText}) + : super( + title: title, + loadingText: loadingText, + uiDesign: UiDesign.MATERIAL); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), + centerTitle: true, ), body: Center( child: loadingWidget, @@ -25,10 +31,14 @@ class TrainInfoLoadingMaterial extends TrainInfoLoading { class TrainInfoErrorMaterial extends TrainInfoError { TrainInfoErrorMaterial({ - 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) { @@ -56,24 +66,32 @@ class TrainInfoErrorMaterial extends TrainInfoError { } } -bool isSmallScreen(BuildContext context) => MediaQuery.of(context).size.height <= 425; +bool isSmallScreen(BuildContext context) => + MediaQuery.of(context).size.height <= 425; class TrainInfoMaterial extends StatelessWidget { final TrainData trainData; final Future Function()? refresh; final void Function()? onViewYesterdayTrain; - TrainInfoMaterial({required this.trainData, this.refresh, this.onViewYesterdayTrain,}); + TrainInfoMaterial({ + required this.trainData, + this.refresh, + this.onViewYesterdayTrain, + }); @override Widget build(BuildContext context) { return Builder( builder: (context) { return Scaffold( - appBar: isSmallScreen(context) ? null : AppBar( - centerTitle: true, - title: Text("Informații despre ${trainData.rank} ${trainData.number}"), - ), + appBar: isSmallScreen(context) + ? null + : AppBar( + centerTitle: true, + title: Text( + "Informații despre ${trainData.rank} ${trainData.number}"), + ), body: Column( children: [ if (isSmallScreen(context)) @@ -82,8 +100,8 @@ class TrainInfoMaterial extends StatelessWidget { left: false, right: false, child: SlimAppBar( - title: 'INFO TREN - ${trainData.rank} ${trainData.number}' - ), + title: + 'INFO TREN - ${trainData.rank} ${trainData.number}'), ), Expanded( child: SafeArea( @@ -94,19 +112,27 @@ class TrainInfoMaterial extends StatelessWidget { child: CustomScrollView( slivers: [ SliverToBoxAdapter( - child: DisplayTrainID(trainData: trainData,), + child: DisplayTrainID( + trainData: trainData, + ), ), SliverToBoxAdapter( - child: DisplayTrainOperator(trainData: trainData,), + child: DisplayTrainOperator( + trainData: trainData, + ), ), SliverPadding( padding: const EdgeInsets.only(left: 2, right: 2), sliver: SliverToBoxAdapter( - child: DisplayTrainRoute(trainData: trainData,), + child: DisplayTrainRoute( + trainData: trainData, + ), ), ), SliverToBoxAdapter( - child: DisplayTrainDeparture(trainData: trainData,), + child: DisplayTrainDeparture( + trainData: trainData, + ), ), // SliverToBoxAdapter( // child: Divider( @@ -115,7 +141,9 @@ class TrainInfoMaterial extends StatelessWidget { // ), // ), SliverToBoxAdapter( - child: DisplayTrainLastInfo(trainData: trainData,), + child: DisplayTrainLastInfo( + trainData: trainData, + ), ), SliverToBoxAdapter( child: IntrinsicHeight( @@ -123,8 +151,15 @@ class TrainInfoMaterial extends StatelessWidget { children: [ // Expanded(child: DisplayTrainNextStop(trainData: trainData,)), // Expanded(child: DisplayTrainDestination(trainData: trainData,)), - Expanded(child: DisplayTrainRouteDuration(trainData: trainData,)), - Expanded(child: DisplayTrainRouteDistance(trainData: trainData,),), + Expanded( + child: DisplayTrainRouteDuration( + trainData: trainData, + )), + Expanded( + child: DisplayTrainRouteDistance( + trainData: trainData, + ), + ), ], ), ), @@ -146,9 +181,13 @@ class TrainInfoMaterial extends StatelessWidget { height: isSmallScreen(context) ? 8 : 16, ), ), - if (onViewYesterdayTrain != null && trainData.stations.first.departure!.scheduleTime.compareTo(DateTime.now()) > 0) ...[ + if (onViewYesterdayTrain != null && + trainData.stations.first.departure!.scheduleTime + .compareTo(DateTime.now()) > + 0) ...[ SliverToBoxAdapter( - child: DisplayTrainYesterdayWarningMaterial(onViewYesterdayTrain!), + child: DisplayTrainYesterdayWarningMaterial( + onViewYesterdayTrain!), ), SliverToBoxAdapter( child: Divider( @@ -191,19 +230,24 @@ class DisplayTrainID extends StatelessWidget { TextSpan( text: trainData.rank, style: TextStyle( - color: trainData.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null, + color: trainData.rank.startsWith('IR') + ? Color.fromARGB(255, 255, 0, 0) + : null, ), ), TextSpan(text: ' '), - TextSpan(text: trainData.number,), + TextSpan( + text: trainData.number, + ), ], ), - style: (isSmallScreen(context) - ? Theme.of(context).textTheme.headline4 - : Theme.of(context).textTheme.headline3)?.copyWith( - color: Theme.of(context).textTheme.bodyText2?.color, - fontWeight: FontWeight.bold, - ), + style: (isSmallScreen(context) + ? Theme.of(context).textTheme.headline4 + : Theme.of(context).textTheme.headline3) + ?.copyWith( + color: Theme.of(context).textTheme.bodyText2?.color, + fontWeight: FontWeight.bold, + ), textAlign: TextAlign.center, ); } @@ -219,9 +263,9 @@ class DisplayTrainOperator extends StatelessWidget { return Text( trainData.operator, style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontStyle: FontStyle.italic, - fontSize: isSmallScreen(context) ? 12 : 14, - ), + fontStyle: FontStyle.italic, + fontSize: isSmallScreen(context) ? 12 : 14, + ), textAlign: TextAlign.center, ); } @@ -243,8 +287,8 @@ class DisplayTrainRoute extends StatelessWidget { child: Text( trainData.route.from, style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: 16, - ), + fontSize: 16, + ), ), ), ), @@ -257,8 +301,8 @@ class DisplayTrainRoute extends StatelessWidget { child: Text( trainData.route.to, style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: 16, - ), + fontSize: 16, + ), textAlign: TextAlign.right, ), ), @@ -282,10 +326,10 @@ 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: Theme.of(context).textTheme.bodyText2?.copyWith( - fontStyle: FontStyle.italic, - fontWeight: FontWeight.w200, - fontSize: isSmallScreen(context) ? 14 : 16, - ), + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w200, + fontSize: isSmallScreen(context) ? 14 : 16, + ), textAlign: TextAlign.center, ), ); @@ -315,9 +359,9 @@ class DisplayTrainLastInfo extends StatelessWidget { child: Text( "Ultima informație", style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 20 : 22, - fontWeight: FontWeight.bold, - ), + fontSize: isSmallScreen(context) ? 20 : 22, + fontWeight: FontWeight.bold, + ), ), ), ), @@ -328,19 +372,21 @@ class DisplayTrainLastInfo extends StatelessWidget { child: Text( trainData.status!.station, style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 16 : 18, - ), + fontSize: isSmallScreen(context) ? 16 : 18, + ), textAlign: TextAlign.left, ), ), - Expanded(child: Container(),), + Expanded( + child: Container(), + ), Padding( padding: const EdgeInsets.all(4), - child: Text( + child: Text( stateToString(trainData.status!.state), style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 16 : 18, - ), + fontSize: isSmallScreen(context) ? 16 : 18, + ), textAlign: TextAlign.right, ), ), @@ -359,7 +405,9 @@ class DisplayTrainLastInfo extends StatelessWidget { // ); // }, // ), - Expanded(child: Container(),), + Expanded( + child: Container(), + ), Builder( builder: (context) { final data = trainData.status!.delay; @@ -370,19 +418,20 @@ class DisplayTrainLastInfo extends StatelessWidget { if (data > 0) { return Text( "$data ${data == 1 ? 'minut' : 'minute'} întârziere", - style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 14 : 16, - color: Colors.red.shade300, - ), + style: + Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 14 : 16, + color: Colors.red.shade300, + ), ); - } - else { + } else { return Text( "${-data} ${data == -1 ? 'minut' : 'minute'} mai devreme", - style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 14 : 16, - color: Colors.green.shade300, - ), + style: + Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 14 : 16, + color: Colors.green.shade300, + ), ); } }, @@ -408,7 +457,7 @@ class DisplayTrainLastInfo extends StatelessWidget { // future: trainData.nextStop.stationName, // builder: (context, snapshot) { // if (!snapshot.hasData) return Container(height: 0,); -// +// // return Card( // child: Center( // child: Padding( @@ -500,9 +549,9 @@ class DisplayTrainDestination extends StatelessWidget { child: Text( "Destinația", style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 20 : 22, - fontWeight: FontWeight.bold, - ), + fontSize: isSmallScreen(context) ? 20 : 22, + fontWeight: FontWeight.bold, + ), textAlign: TextAlign.center, ), ), @@ -511,18 +560,21 @@ class DisplayTrainDestination extends StatelessWidget { child: Text( destination.name, style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 18 : 20, - fontWeight: FontWeight.w500, - ), + fontSize: isSmallScreen(context) ? 18 : 20, + fontWeight: FontWeight.w500, + ), textAlign: TextAlign.center, ), ), Builder( builder: (context) { final arrival = destination.arrival!.scheduleTime.toLocal(); - final delay = trainData.stations.last.arrival!.status?.delay ?? 0; - final arrivalWithDelay = arrival.add(Duration(minutes: delay)); - final arrivalWithDelayString = '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}'; + final delay = + trainData.stations.last.arrival!.status?.delay ?? 0; + 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( @@ -542,25 +594,30 @@ class DisplayTrainDestination extends StatelessWidget { children: [ TextSpan(text: ' '), TextSpan( - text: '${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}', - 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 ? Colors.red.shade300 : Colors.green.shade300, + color: delay > 0 + ? Colors.red.shade300 + : Colors.green.shade300, ), ), ] ], ), style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 14 : 16, - ), + fontSize: isSmallScreen(context) ? 14 : 16, + ), textAlign: TextAlign.center, ), ], @@ -592,16 +649,16 @@ class DisplayTrainRouteDistance extends StatelessWidget { Text( "Distanța rutei", style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 20 : 22, - fontWeight: FontWeight.bold, - ), + fontSize: isSmallScreen(context) ? 20 : 22, + fontWeight: FontWeight.bold, + ), textAlign: TextAlign.center, ), Text( "${trainData.stations.last.km} km", style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 18 : 20, - ), + fontSize: isSmallScreen(context) ? 18 : 20, + ), textAlign: TextAlign.center, ), ], @@ -629,22 +686,26 @@ class DisplayTrainRouteDuration extends StatelessWidget { Text( "Durata rutei", style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 20 : 22, - fontWeight: FontWeight.bold, - ), + fontSize: isSmallScreen(context) ? 20 : 22, + fontWeight: FontWeight.bold, + ), textAlign: TextAlign.center, ), Builder( builder: (context) { - var duration = trainData.stations.last.arrival!.scheduleTime.difference(trainData.stations.first.departure!.scheduleTime); + var duration = trainData.stations.last.arrival!.scheduleTime + .difference( + 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"); + if (duration.inDays == 1) + durationString.write("1 zi"); + else + durationString.write("${duration.inDays} zile"); duration -= Duration(days: duration.inDays); } @@ -653,8 +714,10 @@ class DisplayTrainRouteDuration extends StatelessWidget { durationString.write(", "); } firstWritten = true; - if (duration.inHours == 1) durationString.write("1 oră"); - else durationString.write("${duration.inHours} ore"); + if (duration.inHours == 1) + durationString.write("1 oră"); + else + durationString.write("${duration.inHours} ore"); duration -= Duration(hours: duration.inHours); } @@ -663,16 +726,18 @@ class DisplayTrainRouteDuration extends StatelessWidget { durationString.write(", "); } firstWritten = true; - if (duration.inMinutes == 1) durationString.write("1 minut"); - else durationString.write("${duration.inMinutes} minute"); + 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, - ), + fontSize: isSmallScreen(context) ? 18 : 20, + ), textAlign: TextAlign.center, ); }, @@ -685,8 +750,10 @@ class DisplayTrainRouteDuration extends StatelessWidget { } } -class DisplayTrainYesterdayWarningMaterial extends DisplayTrainYesterdayWarningCommon { - DisplayTrainYesterdayWarningMaterial(void Function() onViewYesterdayTrain) : super(onViewYesterdayTrain); +class DisplayTrainYesterdayWarningMaterial + extends DisplayTrainYesterdayWarningCommon { + DisplayTrainYesterdayWarningMaterial(void Function() onViewYesterdayTrain) + : super(onViewYesterdayTrain); @override Widget build(BuildContext context) { @@ -698,7 +765,9 @@ class DisplayTrainYesterdayWarningMaterial extends DisplayTrainYesterdayWarningC child: Text.rich( TextSpan( children: [ - TextSpan(text: DisplayTrainYesterdayWarningCommon.trainDidNotDepart,), + TextSpan( + text: DisplayTrainYesterdayWarningCommon.trainDidNotDepart, + ), TextSpan(text: '\n'), TextSpan( text: DisplayTrainYesterdayWarningCommon.seeYesterdayTrain, @@ -730,6 +799,12 @@ class DisplayTrainStations extends StatelessWidget { return IndexedSemantics( child: DisplayTrainStation( station: trainData.stations[index], + onTap: () { + Navigator.of(context).pushNamed( + ViewStationPage.routeName, + arguments: trainData.stations[index].name, + ); + }, ), index: index, ); @@ -740,4 +815,3 @@ class DisplayTrainStations extends StatelessWidget { ); } } - 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 a851a7c..9e5e72c 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 @@ -1,163 +1,92 @@ import 'package:flutter/material.dart'; import 'package:info_tren/models/train_data.dart'; -import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart' show isSmallScreen; +import 'package:info_tren/components/badge.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; class DisplayTrainStation extends StatelessWidget { final Station station; + final void Function()? onTap; - DisplayTrainStation({required this.station}); + DisplayTrainStation({required this.station, this.onTap}); @override Widget build(BuildContext context) { return Card( - child: Padding( - padding: const EdgeInsets.all(2), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - flex: 1, - child: Align( - alignment: Alignment.centerLeft, - child: Builder( - builder: (context) { - final departureStatus = station.departure?.status; - final arrivalStatus = station.arrival?.status; - int delay; - bool real; - if (departureStatus == null) { - delay = arrivalStatus?.delay ?? 0; - real = arrivalStatus?.real ?? false; - } - else if (arrivalStatus == null) { - delay = departureStatus.delay; - real = departureStatus.real; - } - else { - delay = departureStatus.delay; - real = departureStatus.real; - if (!real && arrivalStatus.real) { - delay = arrivalStatus.delay; - real = arrivalStatus.real; + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + flex: 1, + child: Align( + alignment: Alignment.centerLeft, + child: Builder( + builder: (context) { + final departureStatus = station.departure?.status; + final arrivalStatus = station.arrival?.status; + int delay; + bool real; + if (departureStatus == null) { + delay = arrivalStatus?.delay ?? 0; + real = arrivalStatus?.real ?? false; + } + else if (arrivalStatus == null) { + delay = departureStatus.delay; + real = departureStatus.real; } + else { + delay = departureStatus.delay; + real = departureStatus.real; + if (!real && arrivalStatus.real) { + delay = arrivalStatus.delay; + real = arrivalStatus.real; + } + } + + final isDelayed = delay > 0 && real == true; + final isOnTime = delay <= 0 && real == true; + final isNotScheduled = false; + + return MaterialBadge( + text: station.km.toString(), + caption: 'km', + isNotScheduled: isNotScheduled, + isDelayed: isDelayed, + isOnTime: isOnTime, + ); } - - final isDelayed = delay > 0 && real == true; - final isOnTime = delay <= 0 && real == true; - final isNotScheduled = false; - - return Badge( - text: station.km.toString(), - caption: 'km', - isNotScheduled: isNotScheduled, - isDelayed: isDelayed, - isOnTime: isOnTime, - ); - } + ), ), ), - ), - Title( - station: station, - ), - Expanded( - flex: 1, - child: (station.platform == null) - ? Container() - : Align( - alignment: Alignment.centerRight, - child: Badge(text: station.platform!, caption: 'linia',), - ), - ), - ], - ), - Time( - station: station, - ), - Delay( - station: station, - ), - ], - ), - ), - ); - } -} - -class Badge extends StatelessWidget { - final String text; - final String caption; - final bool isNotScheduled; - final bool isOnTime; - final bool isDelayed; - - Badge({ - required this.text, - required this.caption, - this.isNotScheduled = false, - this.isOnTime = false, - this.isDelayed = false, - }); - - @override - Widget build(BuildContext context) { - Color foregroundColor = Colors.white70; - Color? backgroundColor; - - if (isNotScheduled) { - foregroundColor = Colors.orange.shade300; - backgroundColor = Colors.orange.shade900.withOpacity(0.3); - } - else if (isOnTime) { - foregroundColor = Colors.green.shade300; - backgroundColor = Colors.green.shade900.withOpacity(0.3); - } - else if (isDelayed) { - foregroundColor = Colors.red.shade300; - backgroundColor = Colors.red.shade900.withOpacity(0.3); - } - - return Padding( - padding: const EdgeInsets.all(8), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - width: 2, - color: foregroundColor, - ), - color: backgroundColor, - ), - width: isSmallScreen(context) ? 42 : 48, - height: isSmallScreen(context) ? 42 : 48, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Center( - child: Text( - text, - style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: isSmallScreen(context) ? 16 : 20, - fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, - color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, + Title( + station: station, + ), + Expanded( + flex: 1, + child: (station.platform == null) + ? Container() + : Align( + alignment: Alignment.centerRight, + child: MaterialBadge(text: station.platform!, caption: 'linia',), + ), ), - textAlign: TextAlign.center, - ), + ], ), - ), - Text( - caption, - style: Theme.of(context).textTheme.bodyText2?.copyWith( - fontSize: 10, - color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, + Time( + station: station, ), - ), - ], + Delay( + station: station, + ), + ], + ), ), ), ); diff --git a/pubspec.yaml b/pubspec.yaml index fd87bc8..33cf28f 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.4 +version: 2.7.6 environment: sdk: ">=2.15.0 <3.0.0"