Kenneth Bruen
2 years ago
30 changed files with 3062 additions and 612 deletions
@ -0,0 +1,59 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:hooks_riverpod/hooks_riverpod.dart'; |
||||
import 'package:info_tren/components/badge/badge_cupertino.dart'; |
||||
import 'package:info_tren/components/badge/badge_fluent.dart'; |
||||
import 'package:info_tren/components/badge/badge_material.dart'; |
||||
import 'package:info_tren/models.dart'; |
||||
import 'package:info_tren/providers.dart'; |
||||
|
||||
class Badge extends ConsumerWidget { |
||||
final String text; |
||||
final String caption; |
||||
final bool isNotScheduled; |
||||
final bool isOnTime; |
||||
final bool isDelayed; |
||||
|
||||
const Badge({ |
||||
super.key, |
||||
required this.text, |
||||
required this.caption, |
||||
this.isNotScheduled = false, |
||||
this.isOnTime = false, |
||||
this.isDelayed = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context, WidgetRef ref) { |
||||
final uiDesign = ref.watch(uiDesignProvider); |
||||
|
||||
switch (uiDesign) { |
||||
case UiDesign.MATERIAL: |
||||
return MaterialBadge( |
||||
text: text, |
||||
caption: caption, |
||||
isNotScheduled: isNotScheduled, |
||||
isOnTime: isOnTime, |
||||
isDelayed: isDelayed, |
||||
); |
||||
case UiDesign.CUPERTINO: |
||||
return CupertinoBadge( |
||||
text: text, |
||||
caption: caption, |
||||
isNotScheduled: isNotScheduled, |
||||
isOnTime: isOnTime, |
||||
isDelayed: isDelayed, |
||||
); |
||||
case UiDesign.FLUENT: |
||||
return FluentBadge( |
||||
text: text, |
||||
caption: caption, |
||||
isNotScheduled: isNotScheduled, |
||||
isOnTime: isOnTime, |
||||
isDelayed: isDelayed, |
||||
); |
||||
default: |
||||
throw UnmatchedUiDesignException(uiDesign); |
||||
} |
||||
} |
||||
} |
@ -1,84 +1,5 @@
|
||||
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; |
||||
|
||||
const MaterialBadge({ |
||||
required this.text, |
||||
required this.caption, |
||||
this.isNotScheduled = false, |
||||
this.isOnTime = false, |
||||
this.isDelayed = false, |
||||
super.key, |
||||
}); |
||||
|
||||
@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: <Widget>[ |
||||
Expanded( |
||||
child: Center( |
||||
child: Text( |
||||
text, |
||||
style: Theme.of(context).textTheme.bodyMedium?.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.bodyMedium?.copyWith( |
||||
fontSize: 10, |
||||
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class CupertinoBadge extends StatelessWidget { |
||||
final String text; |
@ -0,0 +1,80 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
|
||||
|
||||
class FluentBadge extends StatelessWidget { |
||||
final String text; |
||||
final String caption; |
||||
final bool isNotScheduled; |
||||
final bool isOnTime; |
||||
final bool isDelayed; |
||||
|
||||
const FluentBadge({ |
||||
required this.text, |
||||
required this.caption, |
||||
this.isNotScheduled = false, |
||||
this.isOnTime = false, |
||||
this.isDelayed = false, |
||||
super.key, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
Color foregroundColor = FluentTheme.of(context).activeColor; |
||||
Color? backgroundColor; |
||||
|
||||
if (isNotScheduled) { |
||||
foregroundColor = const Color.fromRGBO(225, 175, 30, 1); |
||||
backgroundColor = const Color.fromRGBO(80, 40, 10, 1); |
||||
} |
||||
else if (isOnTime) { |
||||
foregroundColor = const Color.fromRGBO(130, 175, 65, 1); |
||||
backgroundColor = const Color.fromRGBO(40, 80, 10, 1); |
||||
} |
||||
else if (isDelayed) { |
||||
foregroundColor = const Color.fromRGBO(225, 75, 30, 1); |
||||
backgroundColor = const 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: <Widget>[ |
||||
Expanded( |
||||
child: Center( |
||||
child: Text( |
||||
text, |
||||
style: FluentTheme.of(context).typography.bodyLarge?.copyWith( |
||||
fontSize: 20, |
||||
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, |
||||
color: MediaQuery.of(context).boldText ? Colors.white : foregroundColor, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
), |
||||
Text( |
||||
caption, |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 12, |
||||
color: MediaQuery.of(context).boldText ? Colors.white : foregroundColor, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,79 @@
|
||||
import 'package:flutter/material.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; |
||||
|
||||
const MaterialBadge({ |
||||
required this.text, |
||||
required this.caption, |
||||
this.isNotScheduled = false, |
||||
this.isOnTime = false, |
||||
this.isDelayed = false, |
||||
super.key, |
||||
}); |
||||
|
||||
@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: <Widget>[ |
||||
Expanded( |
||||
child: Center( |
||||
child: Text( |
||||
text, |
||||
style: Theme.of(context).textTheme.bodyMedium?.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.bodyMedium?.copyWith( |
||||
fontSize: 10, |
||||
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,26 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
import 'package:info_tren/components/loading/loading.dart'; |
||||
|
||||
class LoadingFluent extends LoadingCommon { |
||||
const LoadingFluent({required super.text, super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Center( |
||||
child: Column( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
const Padding( |
||||
padding: EdgeInsets.all(8.0), |
||||
child: ProgressRing(), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Text(text), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,84 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; |
||||
import 'package:info_tren/models.dart'; |
||||
|
||||
class SelectTrainSuggestionsFluent extends SelectTrainSuggestionsShared { |
||||
const SelectTrainSuggestionsFluent({ |
||||
super.key, |
||||
required super.choices, |
||||
required super.onTrainSelected, |
||||
super.currentInput, |
||||
}); |
||||
|
||||
@override |
||||
Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(0), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Button( |
||||
child: Text(getUseCurrentInputWidgetText(currentInput)), |
||||
onPressed: () => onTrainSelected(currentInput), |
||||
), |
||||
], |
||||
) |
||||
), |
||||
const Divider(), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class OperatorAutocompleteTileFluent extends OperatorAutocompleteTile { |
||||
const OperatorAutocompleteTileFluent({ |
||||
Key? key, |
||||
required String operatorName, |
||||
required void Function(String) onTrainSelected, |
||||
required TrainsResult train |
||||
}): super( |
||||
onTrainSelected: onTrainSelected, |
||||
operatorName: operatorName, |
||||
train: train, |
||||
key: key, |
||||
); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
GestureDetector( |
||||
onTap: () { |
||||
onTrainSelected(train.number); |
||||
}, |
||||
child: Padding( |
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 4), |
||||
child: SizedBox( |
||||
width: double.infinity, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.stretch, |
||||
children: <Widget>[ |
||||
Text( |
||||
operatorName, |
||||
style: FluentTheme.of(context).typography.body?.copyWith(fontSize: 10, fontWeight: FontWeight.w200), |
||||
textAlign: TextAlign.left, |
||||
), |
||||
Text( |
||||
"${train.rank} ${train.number}", |
||||
textAlign: TextAlign.left, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
), |
||||
const Divider(), |
||||
], |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,73 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
import 'package:info_tren/pages/main/main_page.dart'; |
||||
|
||||
class MainPageFluent extends MainPageShared { |
||||
const MainPageFluent({super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return NavigationView( |
||||
appBar: NavigationAppBar( |
||||
automaticallyImplyLeading: false, |
||||
title: Text(pageTitle), |
||||
// centerTitle: true, |
||||
// actions: [ |
||||
// PopupMenuButton<int>( |
||||
// icon: const Icon(Icons.more_vert), |
||||
// tooltip: moreOptionsText, |
||||
// itemBuilder: (_) => popupMenu.asMap().entries.map((e) => PopupMenuItem( |
||||
// value: e.key, |
||||
// child: Text(e.value.name), |
||||
// )).toList(), |
||||
// onSelected: (index) { |
||||
// popupMenu[index].action?.call(context); |
||||
// }, |
||||
// ), |
||||
// ], |
||||
), |
||||
content: SafeArea( |
||||
child: Center( |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: options.map((option) => HoverButton( |
||||
onPressed: option.action != null ? () => option.action!(context) : null, |
||||
builder: (context, states) { |
||||
return Card( |
||||
backgroundColor: option.action != null ? (states.isHovering ? FluentTheme.of(context).accentColor.dark : FluentTheme.of(context).accentColor) : null, |
||||
child: Column( |
||||
children: [ |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Text( |
||||
option.name, |
||||
style: FluentTheme.of(context) |
||||
.typography |
||||
.bodyLarge |
||||
?.copyWith( |
||||
color: |
||||
FluentTheme.of(context).activeColor, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Text(option.description!), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
}, |
||||
)).map((w) => Padding( |
||||
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), |
||||
child: SizedBox( |
||||
width: double.infinity, |
||||
child: w, |
||||
), |
||||
)).toList(), |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,59 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; |
||||
|
||||
class SelectStationPageFluent extends SelectStationPageShared { |
||||
const SelectStationPageFluent({super.key}); |
||||
|
||||
@override |
||||
State<StatefulWidget> createState() => SelectStationPageStateFluent(); |
||||
} |
||||
|
||||
class SelectStationPageStateFluent extends SelectStationPageState { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return NavigationView( |
||||
appBar: const NavigationAppBar( |
||||
title: Text(SelectStationPageState.pageTitle), |
||||
// centerTitle: true, |
||||
), |
||||
content: SafeArea( |
||||
bottom: false, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: [ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: TextBox( |
||||
controller: textEditingController, |
||||
autofocus: true, |
||||
placeholder: SelectStationPageState.textFieldLabel, |
||||
textInputAction: TextInputAction.search, |
||||
onChanged: onTextChanged, |
||||
), |
||||
), |
||||
Expanded( |
||||
child: ListView.builder( |
||||
itemBuilder: (context, index) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
ListTile( |
||||
// dense: true, |
||||
title: Text(filteredStations[index]), |
||||
onPressed: () => onSuggestionSelected(filteredStations[index]), |
||||
), |
||||
const Divider( |
||||
size: 1, |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
itemCount: filteredStations.length, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,267 @@
|
||||
import 'dart:math'; |
||||
import 'dart:ui'; |
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
import 'package:hooks_riverpod/hooks_riverpod.dart'; |
||||
import 'package:info_tren/components/badge/badge.dart'; |
||||
import 'package:info_tren/components/loading/loading.dart'; |
||||
import 'package:info_tren/components/refresh_future_builder.dart'; |
||||
import 'package:info_tren/models.dart'; |
||||
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; |
||||
import 'package:info_tren/providers.dart'; |
||||
|
||||
class ViewStationPageFluent extends ViewStationPageShared { |
||||
const ViewStationPageFluent({super.key, required super.tab, required super.setTab}); |
||||
|
||||
@override |
||||
Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future<StationData> Function()) _, RefreshFutureBuilderSnapshot<StationData> snapshot) { |
||||
return NavigationView( |
||||
appBar: NavigationAppBar( |
||||
title: Consumer( |
||||
builder: (context, ref, _) { |
||||
final stationName = ref.watch(viewStationArgumentsProvider.select((value) => value.stationName)); |
||||
return Text(snapshot.hasData ? snapshot.data!.stationName : stationName); |
||||
} |
||||
), |
||||
), |
||||
content: snapshot.state == RefreshFutureBuilderState.waiting || snapshot.state == RefreshFutureBuilderState.refreshError |
||||
? const Loading(text: ViewStationPageShared.loadingText,) |
||||
: snapshot.state == RefreshFutureBuilderState.error |
||||
? Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Center( |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
Icon( |
||||
FluentIcons.error, |
||||
size: 32, |
||||
color: Colors.red.normal, |
||||
), |
||||
const Text( |
||||
ViewStationPageShared.errorText, |
||||
style: TextStyle( |
||||
inherit: true, |
||||
fontSize: 32, |
||||
), |
||||
), |
||||
Text( |
||||
snapshot.error.toString(), |
||||
style: FluentTheme.of(context).typography.subtitle, |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Button( |
||||
onPressed: () { |
||||
refresh(); |
||||
}, |
||||
child: const Text(ViewStationPageShared.retryText), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
) |
||||
: null, |
||||
pane: snapshot.hasData ? NavigationPane( |
||||
onChanged: onTabChange, |
||||
selected: tab.index, |
||||
items: [ |
||||
PaneItem( |
||||
body: CustomScrollView( |
||||
slivers: [ |
||||
SliverToBoxAdapter(child: SafeArea(left: false, bottom: false, right: false,child: Container(),),), |
||||
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, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
title: const Text(ViewStationPageShared.arrivals), |
||||
icon: const Icon(FluentIcons.arrow_down_right8), |
||||
), |
||||
PaneItem( |
||||
body: CustomScrollView( |
||||
slivers: [ |
||||
SliverToBoxAdapter(child: SafeArea(left: false, bottom: false, right: false,child: Container(),),), |
||||
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, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
title: const Text(ViewStationPageShared.departures), |
||||
icon: const Icon(FluentIcons.arrow_up_right8), |
||||
), |
||||
], |
||||
) : null, |
||||
// bottomNavigationBar: snapshot.hasData ? BottomNavigationBar( |
||||
// items: const [ |
||||
// BottomNavigationBarItem( |
||||
// icon: Icon(Icons.arrow_downward), |
||||
// label: ViewStationPageShared.arrivals, |
||||
// ), |
||||
// BottomNavigationBarItem( |
||||
// icon: Icon(Icons.arrow_upward), |
||||
// label: ViewStationPageShared.departures, |
||||
// ), |
||||
// ], |
||||
// currentIndex: tab.index, |
||||
// onTap: onTabChange, |
||||
// ) : null, |
||||
); |
||||
} |
||||
|
||||
Widget buildStationItem(BuildContext context, StationArrDep item, {required bool arrival}) { |
||||
return HoverButton( |
||||
onPressed: () => onTrainTapped(context, item.train), |
||||
builder: (context, states) { |
||||
return Container( |
||||
color: item.status.cancelled |
||||
? Colors.red.withAlpha(100) |
||||
: states.isPressing |
||||
? FluentTheme.of(context).scaffoldBackgroundColor |
||||
: states.isHovering |
||||
? FluentTheme.of(context).inactiveBackgroundColor |
||||
: 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: const [ |
||||
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; |
||||
|
||||
return Text( |
||||
'${newTime.toLocal().hour.toString().padLeft(2, '0')}:${newTime.toLocal().minute.toString().padLeft(2, '0')}', |
||||
style: TextStyle( |
||||
inherit: true, |
||||
fontFeatures: const [ |
||||
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') ? const Color.fromARGB(255, 255, 0, 0) : null, |
||||
), |
||||
), |
||||
const TextSpan(text: ' '), |
||||
TextSpan(text: item.train.number,), |
||||
], |
||||
), |
||||
), |
||||
subtitle: Text.rich( |
||||
TextSpan( |
||||
children: [ |
||||
TextSpan( |
||||
text: item.status.cancelled |
||||
? (arrival ? ViewStationPageShared.cancelledArrival : ViewStationPageShared.cancelledDeparture) |
||||
: item.time.add(Duration(minutes: max(0, item.status.delay))).compareTo(DateTime.now()) < 0 |
||||
? (arrival ? ViewStationPageShared.arrivedFrom : ViewStationPageShared.departedTo) |
||||
: (arrival ? ViewStationPageShared.arrivesFrom : ViewStationPageShared.departsTo) |
||||
), |
||||
const TextSpan(text: ' '), |
||||
TextSpan(text: item.train.terminus), |
||||
if (item.status.delay != 0) ...[ |
||||
const 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) |
||||
const TextSpan(text: ' și '), |
||||
], |
||||
TextSpan(text: (item.status.delay.abs() % 60).toString()), |
||||
TextSpan(text: item.status.delay.abs() > 1 ? ' minute' : ' minut'), |
||||
const 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: Badge( |
||||
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); |
||||
} |
||||
} |
@ -0,0 +1,46 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
import 'package:flutter/services.dart'; |
||||
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; |
||||
|
||||
class SelectTrainPageFluent extends SelectTrainPageShared { |
||||
const SelectTrainPageFluent({super.key}); |
||||
|
||||
@override |
||||
State<SelectTrainPageShared> createState() => SelectTrainPageStateFluent(); |
||||
} |
||||
|
||||
class SelectTrainPageStateFluent extends SelectTrainPageState { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return NavigationView( |
||||
appBar: NavigationAppBar( |
||||
title: Text(pageTitle), |
||||
), |
||||
content: SafeArea( |
||||
bottom: false, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: TextBox( |
||||
controller: trainNoController, |
||||
autofocus: true, |
||||
placeholder: textFieldLabel, |
||||
textInputAction: TextInputAction.search, |
||||
keyboardType: TextInputType.number, |
||||
onChanged: (_) => onTextChanged(), |
||||
inputFormatters: [ |
||||
FilteringTextInputFormatter.digitsOnly, |
||||
], |
||||
), |
||||
), |
||||
Expanded( |
||||
child: suggestionsList, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,807 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
import 'package:flutter/gestures.dart'; |
||||
import 'package:info_tren/models.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_fluent_DisplayTrainStation.dart'; |
||||
import 'package:info_tren/utils/state_to_string.dart'; |
||||
|
||||
class TrainInfoLoadingFluent extends TrainInfoLoadingShared { |
||||
TrainInfoLoadingFluent({required super.title, super.loadingText, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return NavigationView( |
||||
appBar: NavigationAppBar( |
||||
title: Text(title), |
||||
), |
||||
content: Center( |
||||
child: loadingWidget, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class TrainInfoErrorFluent extends TrainInfoErrorShared { |
||||
const TrainInfoErrorFluent({ |
||||
required super.error, |
||||
required super.title, |
||||
super.refresh, |
||||
super.key, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return NavigationView( |
||||
appBar: NavigationAppBar( |
||||
title: Text(title), |
||||
), |
||||
content: Center( |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
Text(error.toString()), |
||||
if (refresh != null) |
||||
Padding( |
||||
padding: const EdgeInsets.all(8), |
||||
child: Button( |
||||
child: const Text('Retry'), |
||||
onPressed: () => refresh!(), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class TrainInfoFluent extends TrainInfoShared { |
||||
const TrainInfoFluent({ |
||||
super.key, |
||||
required super.trainData, |
||||
super.isRefreshing, |
||||
super.refresh, |
||||
super.onViewYesterdayTrain, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Builder( |
||||
builder: (context) { |
||||
return NavigationView( |
||||
appBar: NavigationAppBar( |
||||
title: Text( |
||||
"Informații despre ${trainData.rank} ${trainData.number}", |
||||
), |
||||
actions: Row( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
if (refresh != null) ...[ |
||||
Center( |
||||
child: SizedBox( |
||||
height: 32, |
||||
width: 32, |
||||
child: IconButton( |
||||
icon: isRefreshing == true ? const ProgressRing() : const Icon( |
||||
FluentIcons.refresh, |
||||
size: 24, |
||||
), |
||||
onPressed: isRefreshing == true ? null : () { |
||||
refresh!(); |
||||
}, |
||||
), |
||||
), |
||||
), |
||||
], |
||||
], |
||||
), |
||||
), |
||||
content: Column( |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: SafeArea( |
||||
bottom: false, |
||||
child: TrainInfoBody( |
||||
trainData: trainData, |
||||
onViewYesterdayTrain: onViewYesterdayTrain, |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class TrainInfoBodyFluent extends TrainInfoBodyShared { |
||||
const TrainInfoBodyFluent({ |
||||
super.key, |
||||
required super.trainData, |
||||
super.onViewYesterdayTrain, |
||||
super.refresh, |
||||
super.isRefreshing, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final mq = MediaQuery.of(context); |
||||
|
||||
if (mq.orientation == Orientation.landscape && mq.size.width >= 1000) { |
||||
return Row( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: [ |
||||
Container( |
||||
constraints: const BoxConstraints( |
||||
minWidth: 400, |
||||
maxWidth: 400, |
||||
), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: [ |
||||
DisplayTrainID(trainData: trainData), |
||||
DisplayTrainOperator(trainData: trainData), |
||||
Padding( |
||||
padding: const EdgeInsets.symmetric(horizontal: 2.0), |
||||
child: DisplayTrainRoute(trainData: trainData), |
||||
), |
||||
DisplayTrainDeparture(trainData: trainData), |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: DisplayTrainLastInfo(trainData: trainData), |
||||
), |
||||
IntrinsicHeight( |
||||
child: Row( |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: DisplayTrainRouteDuration( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
), |
||||
Expanded( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: DisplayTrainRouteDistance( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
const Divider(), |
||||
if (onViewYesterdayTrain != null && |
||||
trainData.stations.first.departure!.scheduleTime |
||||
.compareTo(DateTime.now()) > |
||||
0) |
||||
...[ |
||||
DisplayTrainYesterdayWarningFluent( |
||||
onViewYesterdayTrain!, |
||||
), |
||||
const Divider(), |
||||
], |
||||
], |
||||
), |
||||
), |
||||
Expanded( |
||||
child: CustomScrollView( |
||||
slivers: [ |
||||
DisplayTrainStations( |
||||
trainData: trainData, |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Container( |
||||
height: MediaQuery |
||||
.of(context) |
||||
.viewPadding |
||||
.bottom, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
return CustomScrollView( |
||||
slivers: <Widget>[ |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainID( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainOperator( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
SliverPadding( |
||||
padding: const EdgeInsets.only(left: 2, right: 2), |
||||
sliver: SliverToBoxAdapter( |
||||
child: DisplayTrainRoute( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainDeparture( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainLastInfo( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: IntrinsicHeight( |
||||
child: Row( |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: DisplayTrainRouteDuration( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
), |
||||
Expanded( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: DisplayTrainRouteDistance( |
||||
trainData: trainData, |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
const SliverToBoxAdapter( |
||||
child: Divider(), |
||||
), |
||||
if (onViewYesterdayTrain != null && |
||||
trainData.stations.first.departure!.scheduleTime |
||||
.compareTo(DateTime.now()) > |
||||
0) ...[ |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainYesterdayWarningFluent( |
||||
onViewYesterdayTrain!), |
||||
), |
||||
const SliverToBoxAdapter( |
||||
child: Divider(), |
||||
), |
||||
], |
||||
DisplayTrainStations( |
||||
trainData: trainData, |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Container( |
||||
height: MediaQuery |
||||
.of(context) |
||||
.viewPadding |
||||
.bottom, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainID extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
const DisplayTrainID({required this.trainData, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Text.rich( |
||||
TextSpan( |
||||
children: [ |
||||
TextSpan( |
||||
text: trainData.rank, |
||||
style: TextStyle( |
||||
color: trainData.rank.startsWith('IR') |
||||
? const Color.fromARGB(255, 255, 0, 0) |
||||
: null, |
||||
), |
||||
), |
||||
const TextSpan(text: ' '), |
||||
TextSpan( |
||||
text: trainData.number, |
||||
), |
||||
], |
||||
), |
||||
style: FluentTheme.of(context).typography.title?.copyWith( |
||||
color: FluentTheme.of(context).typography.body?.color, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainOperator extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
const DisplayTrainOperator({required this.trainData, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Text( |
||||
trainData.operator, |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontStyle: FontStyle.italic, |
||||
fontSize: 14, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRoute extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
const DisplayTrainRoute({required this.trainData, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Row( |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.route.from, |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
), |
||||
const Center(child: Text("-")), |
||||
Expanded( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.route.to, |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
textAlign: TextAlign.right, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainDeparture extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
const DisplayTrainDeparture({required this.trainData, super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Text( |
||||
// "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: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontStyle: FontStyle.italic, |
||||
fontWeight: FontWeight.w200, |
||||
fontSize: 16, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainLastInfo extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
const DisplayTrainLastInfo({required this.trainData, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (trainData.status == null) { |
||||
return Container(); |
||||
} |
||||
|
||||
return Card( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Text( |
||||
"Ultima informație", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Row( |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.status!.station, |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 18, |
||||
), |
||||
textAlign: TextAlign.left, |
||||
), |
||||
), |
||||
Expanded( |
||||
child: Container(), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
stateToString(trainData.status!.state), |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 18, |
||||
), |
||||
textAlign: TextAlign.right, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Row( |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: Container(), |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
final data = trainData.status!.delay; |
||||
if (data == 0) { |
||||
return Container(); |
||||
} |
||||
|
||||
if (data > 0) { |
||||
return Text( |
||||
"$data ${data == 1 ? 'minut' : 'minute'} întârziere", |
||||
style: |
||||
FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 16, |
||||
color: Colors.red.lighter, |
||||
// color: Colors.red.shade300, |
||||
), |
||||
); |
||||
} else { |
||||
return Text( |
||||
"${-data} ${data == -1 ? 'minut' : 'minute'} mai devreme", |
||||
style: |
||||
FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 16, |
||||
color: Colors.green.lighter, |
||||
// color: Colors.green.shade300, |
||||
), |
||||
); |
||||
} |
||||
}, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainDestination extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
const DisplayTrainDestination({required this.trainData, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final destination = trainData.stations.last; |
||||
|
||||
return Card( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
"Destinația", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), |
||||
child: Text( |
||||
destination.name, |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 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")}'; |
||||
// const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
// Text( |
||||
// "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", |
||||
// style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
// fontSize: isSmallScreen(context) ? 12 : 14, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
Text.rich( |
||||
TextSpan( |
||||
text: 'la', |
||||
children: [ |
||||
const TextSpan(text: ' '), |
||||
TextSpan( |
||||
text: |
||||
'${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}', |
||||
style: delay == 0 |
||||
? null |
||||
: const TextStyle( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
if (delay != 0) ...[ |
||||
const TextSpan(text: ' '), |
||||
TextSpan( |
||||
text: arrivalWithDelayString, |
||||
style: TextStyle( |
||||
color: delay > 0 |
||||
? Colors.red.lighter |
||||
: Colors.green.lighter, |
||||
// color: delay > 0 |
||||
// ? Colors.red.shade300 |
||||
// : Colors.green.shade300, |
||||
), |
||||
), |
||||
] |
||||
], |
||||
), |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
) |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRouteDistance extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
const DisplayTrainRouteDistance({required this.trainData, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"Distanța rutei", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Text( |
||||
"${trainData.stations.last.km} km", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 20, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRouteDuration extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
const DisplayTrainRouteDuration({required this.trainData, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"Durata rutei", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
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"); |
||||
} |
||||
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: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 20, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainYesterdayWarningFluent |
||||
extends DisplayTrainYesterdayWarningCommon { |
||||
const DisplayTrainYesterdayWarningFluent(super.onViewYesterdayTrain, {super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Text.rich( |
||||
TextSpan( |
||||
children: [ |
||||
const TextSpan( |
||||
text: DisplayTrainYesterdayWarningCommon.trainDidNotDepart, |
||||
), |
||||
const TextSpan(text: '\n'), |
||||
TextSpan( |
||||
text: DisplayTrainYesterdayWarningCommon.seeYesterdayTrain, |
||||
style: TextStyle( |
||||
color: Colors.blue, |
||||
), |
||||
recognizer: TapGestureRecognizer() |
||||
..onTap = onViewYesterdayTrain, |
||||
), |
||||
], |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainStations extends StatelessWidget { |
||||
final TrainData trainData; |
||||
const DisplayTrainStations({required this.trainData, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SliverList( |
||||
delegate: SliverChildBuilderDelegate( |
||||
(context, index) { |
||||
return IndexedSemantics( |
||||
index: index, |
||||
child: DisplayTrainStation( |
||||
station: trainData.stations[index], |
||||
onTap: () { |
||||
Navigator.of(context).pushNamed( |
||||
ViewStationPage.routeName, |
||||
arguments: ViewStationArguments(stationName: trainData.stations[index].name), |
||||
); |
||||
}, |
||||
), |
||||
); |
||||
}, |
||||
childCount: trainData.stations.length, |
||||
addSemanticIndexes: true, |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,447 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart'; |
||||
import 'package:info_tren/components/badge/badge.dart'; |
||||
import 'package:info_tren/models.dart'; |
||||
|
||||
class DisplayTrainStation extends StatelessWidget { |
||||
final Station station; |
||||
final void Function()? onTap; |
||||
|
||||
const DisplayTrainStation({required this.station, this.onTap, super.key,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: HoverButton( |
||||
onPressed: onTap, |
||||
builder: (context, states) { |
||||
return Card( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Row( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
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; |
||||
const 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 Title extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
const Title({ |
||||
required this.station, |
||||
super.key, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Text( |
||||
station.name, |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w500 : FontWeight.w300, |
||||
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Time extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
const Time({ |
||||
required this.station, |
||||
super.key, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (station.arrival == null) { |
||||
// Plecare |
||||
return DepartureTime( |
||||
station: station, |
||||
firstStation: true, |
||||
); |
||||
} |
||||
|
||||
if (station.departure == null) { |
||||
// Sosire |
||||
return ArrivalTime( |
||||
station: station, |
||||
finalStation: true, |
||||
); |
||||
} |
||||
|
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Text( |
||||
"→", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
Container(width: 2,), |
||||
ArrivalTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
StopTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
DepartureTime(station: station,), |
||||
Container(width: 2,), |
||||
Text( |
||||
"→", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class ArrivalTime extends StatelessWidget { |
||||
final Station station; |
||||
final bool finalStation; |
||||
|
||||
const ArrivalTime({ |
||||
required this.station, |
||||
this.finalStation = false, |
||||
super.key, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (station.arrival == null) { |
||||
return Container(); |
||||
} |
||||
if (finalStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Text( |
||||
"→", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
Container(width: 2,), |
||||
const Text("sosire la "), |
||||
ArrivalTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final delay = station.arrival!.status?.delay ?? 0; |
||||
final time = station.arrival!.scheduleTime.toLocal(); |
||||
|
||||
if (delay == 0) { |
||||
return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); |
||||
} |
||||
else if (delay > 0) { |
||||
final oldDate = time; |
||||
final newDate = oldDate.add(Duration(minutes: delay)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
// color: Colors.red.shade300, |
||||
color: Colors.red.lighter, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final oldDate = time; |
||||
final newDate = oldDate.add(Duration(minutes: delay)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
// color: Colors.green.shade300, |
||||
color: Colors.green.lighter, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
class StopTime extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
const StopTime({ |
||||
required this.station, |
||||
super.key, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
const Text( |
||||
"staționează pentru", |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
int stopsForInt = station.stoppingTime!; |
||||
bool minutes = false; |
||||
if (stopsForInt >= 60) { |
||||
stopsForInt ~/= 60; |
||||
minutes = true; |
||||
} |
||||
if (stopsForInt == 1) { |
||||
return Text( |
||||
"1 ${minutes ? 'minut' : 'secundă'}", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else if (stopsForInt < 20) { |
||||
return Text( |
||||
"$stopsForInt ${minutes ? 'minute' : 'secunde'}", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else { |
||||
return Text( |
||||
"$stopsForInt de ${minutes ? 'minute' : 'secunde'}", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
}, |
||||
) |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DepartureTime extends StatelessWidget { |
||||
final Station station; |
||||
final bool firstStation; |
||||
|
||||
const DepartureTime({ |
||||
required this.station, |
||||
this.firstStation = false, |
||||
super.key, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (station.departure == null) { |
||||
return Container(); |
||||
} |
||||
if (firstStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Expanded(child: Container(),), |
||||
const Text("plecare la "), |
||||
DepartureTime(station: station,), |
||||
Container(width: 2,), |
||||
Text( |
||||
"→", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final delay = station.departure!.status?.delay ?? 0; |
||||
final time = station.departure!.scheduleTime.toLocal(); |
||||
|
||||
if (delay == 0) { |
||||
return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); |
||||
} |
||||
else if (delay > 0) { |
||||
final oldDate = time; |
||||
final newDate = oldDate.add(Duration(minutes: delay)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
// color: Colors.red.shade300, |
||||
color: Colors.red.lighter, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final oldDate = time; |
||||
final newDate = oldDate.add(Duration(minutes: delay)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
// color: Colors.green.shade300, |
||||
color: Colors.green.lighter, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
class Delay extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
const Delay({ |
||||
required this.station, |
||||
super.key, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (station.arrival?.status == null && station.departure?.status == null) { |
||||
return Container(); |
||||
} |
||||
var delay = station.arrival?.status?.delay; |
||||
if (station.departure?.status?.real == true) { |
||||
delay = station.departure?.status?.delay; |
||||
} |
||||
|
||||
if (delay == 0 || delay == null) { |
||||
return Container(); |
||||
} else if (delay > 0) { |
||||
return Text( |
||||
"$delay ${delay == 1 ? 'minut' : 'minute'} întârziere", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
// color: Colors.red.shade300, |
||||
color: Colors.red.lighter, |
||||
fontSize: 14, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
else if (delay < 0) { |
||||
return Text( |
||||
"${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme", |
||||
style: FluentTheme.of(context).typography.body?.copyWith( |
||||
// color: Colors.green.shade300, |
||||
color: Colors.green.lighter, |
||||
fontSize: 14, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
|
||||
return Container(); |
||||
} |
||||
} |
Loading…
Reference in new issue