Browse Source

Add Fluent UI for Win, Linux

Also add split screen train view for landscape
master v2.7.9
Kenneth Bruen 2 years ago
parent
commit
0e484bdc16
Signed by: kbruen
GPG Key ID: C1980A470C3EE5B1
  1. 6
      CHANGELOG.txt
  2. 59
      lib/components/badge/badge.dart
  3. 79
      lib/components/badge/badge_cupertino.dart
  4. 80
      lib/components/badge/badge_fluent.dart
  5. 79
      lib/components/badge/badge_material.dart
  6. 3
      lib/components/loading/loading.dart
  7. 26
      lib/components/loading/loading_fluent.dart
  8. 13
      lib/components/select_train_suggestions/select_train_suggestions.dart
  9. 84
      lib/components/select_train_suggestions/select_train_suggestions_fluent.dart
  10. 80
      lib/main.dart
  11. 3
      lib/models/ui_design.dart
  12. 3
      lib/pages/main/main_page.dart
  13. 73
      lib/pages/main/main_page_fluent.dart
  14. 3
      lib/pages/station_arrdep_page/select_station/select_station.dart
  15. 59
      lib/pages/station_arrdep_page/select_station/select_station_fluent.dart
  16. 6
      lib/pages/station_arrdep_page/view_station/view_station.dart
  17. 267
      lib/pages/station_arrdep_page/view_station/view_station_fluent.dart
  18. 4
      lib/pages/station_arrdep_page/view_station/view_station_material.dart
  19. 9
      lib/pages/train_info_page/select_train/select_train.dart
  20. 46
      lib/pages/train_info_page/select_train/select_train_fluent.dart
  21. 195
      lib/pages/train_info_page/view_train/train_info.dart
  22. 853
      lib/pages/train_info_page/view_train/train_info_cupertino.dart
  23. 6
      lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart
  24. 807
      lib/pages/train_info_page/view_train/train_info_fluent.dart
  25. 447
      lib/pages/train_info_page/view_train/train_info_fluent_DisplayTrainStation.dart
  26. 328
      lib/pages/train_info_page/view_train/train_info_material.dart
  27. 6
      lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart
  28. 3
      lib/utils/default_ui_design.dart
  29. 44
      pubspec.lock
  30. 3
      pubspec.yaml

6
CHANGELOG.txt

@ -1,3 +1,9 @@
v2.7.9
Add Fluent UI for Windows and Linux.
Add split view in landscape when viewing a train.
Add error handling and auto refresh when viewing a station's arrivals/departures.
General code fixes and migration (freezed, riverpod).
v2.7.8 v2.7.8
Added cancelled trains in departures/arrivals board. Added cancelled trains in departures/arrivals board.
Selecting train in departures/arrivels board chooses appropriate departure date. Selecting train in departures/arrivels board chooses appropriate departure date.

59
lib/components/badge/badge.dart

@ -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);
}
}
}

79
lib/components/badge.dart → lib/components/badge/badge_cupertino.dart

@ -1,84 +1,5 @@
import 'package:flutter/cupertino.dart'; 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/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 { class CupertinoBadge extends StatelessWidget {
final String text; final String text;

80
lib/components/badge/badge_fluent.dart

@ -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,
),
),
],
),
),
);
}
}

79
lib/components/badge/badge_material.dart

@ -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,
),
),
],
),
),
);
}
}

3
lib/components/loading/loading.dart

@ -1,6 +1,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/loading/loading_cupertino.dart'; import 'package:info_tren/components/loading/loading_cupertino.dart';
import 'package:info_tren/components/loading/loading_fluent.dart';
import 'package:info_tren/components/loading/loading_material.dart'; import 'package:info_tren/components/loading/loading_material.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -19,6 +20,8 @@ class Loading extends ConsumerWidget {
return LoadingMaterial(text: text ?? defaultText,); return LoadingMaterial(text: text ?? defaultText,);
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return LoadingCupertino(text: text ?? defaultText,); return LoadingCupertino(text: text ?? defaultText,);
case UiDesign.FLUENT:
return LoadingFluent(text: text ?? defaultText,);
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }

26
lib/components/loading/loading_fluent.dart

@ -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),
),
],
),
);
}
}

13
lib/components/select_train_suggestions/select_train_suggestions.dart

@ -2,6 +2,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_cupertino.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_cupertino.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_fluent.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_material.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_material.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -31,6 +32,12 @@ class SelectTrainSuggestions extends ConsumerWidget {
onTrainSelected: onTrainSelected, onTrainSelected: onTrainSelected,
currentInput: currentInput, currentInput: currentInput,
); );
case UiDesign.FLUENT:
return SelectTrainSuggestionsFluent(
choices: choices,
onTrainSelected: onTrainSelected,
currentInput: currentInput,
);
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
@ -102,6 +109,12 @@ class OperatorAutocompleteSliver extends ConsumerWidget {
operatorName: operatorName, operatorName: operatorName,
train: train, train: train,
); );
case UiDesign.FLUENT:
return OperatorAutocompleteTileFluent(
onTrainSelected: onTrainSelected,
operatorName: operatorName,
train: train,
);
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }

84
lib/components/select_train_suggestions/select_train_suggestions_fluent.dart

@ -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(),
],
);
}
}

80
lib/main.dart

@ -1,5 +1,10 @@
import 'package:flutter/material.dart'; import 'package:fluent_ui/fluent_ui.dart' as f;
import 'package:flutter/cupertino.dart' as c;
import 'package:flutter/material.dart' as m;
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/models.dart';
import 'package:info_tren/pages/about/about_page.dart'; import 'package:info_tren/pages/about/about_page.dart';
import 'package:info_tren/pages/main/main_page.dart'; import 'package:info_tren/pages/main/main_page.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
@ -54,48 +59,59 @@ Map<String, WidgetBuilder> get routes => {
}, },
}; };
class StartPoint extends StatelessWidget { class StartPoint extends ConsumerWidget {
final String appTitle = 'Info Tren'; final String appTitle = 'Info Tren';
const StartPoint({super.key}); const StartPoint({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
// if (Platform.isIOS || Platform.isMacOS) { final uiDesign = ref.watch(uiDesignProvider);
// return AnnotatedRegion( if (uiDesign == UiDesign.CUPERTINO) {
// value: const SystemUiOverlayStyle( return AnnotatedRegion(
// statusBarBrightness: Brightness.dark, value: const SystemUiOverlayStyle(
// ), statusBarBrightness: c.Brightness.dark,
// child: CupertinoApp( ),
// title: appTitle, child: c.CupertinoApp(
// theme: CupertinoThemeData( title: appTitle,
// primaryColor: Colors.blue.shade600, theme: c.CupertinoThemeData(
// brightness: Brightness.dark, primaryColor: m.Colors.blue.shade600,
// // textTheme: CupertinoTextThemeData( brightness: c.Brightness.dark,
// // textStyle: TextStyle( // textTheme: CupertinoTextThemeData(
// // fontFamily: 'Atkinson Hyperlegible', // textStyle: TextStyle(
// // ), // fontFamily: 'Atkinson Hyperlegible',
// // ), // ),
// ), // ),
// routes: routesByUiDesign(UiDesign.CUPERTINO), ),
// ), routes: routes,
// ); ),
// } );
// else { }
return MaterialApp( else if (uiDesign == UiDesign.FLUENT) {
return f.FluentApp(
title: appTitle,
theme: f.ThemeData(
brightness: f.Brightness.dark,
accentColor: f.Colors.blue,
),
routes: routes,
);
}
else {
return m.MaterialApp(
title: appTitle, title: appTitle,
theme: ThemeData( theme: m.ThemeData(
primarySwatch: Colors.blue, primarySwatch: m.Colors.blue,
colorScheme: ColorScheme.fromSwatch( colorScheme: m.ColorScheme.fromSwatch(
brightness: Brightness.dark, brightness: m.Brightness.dark,
primarySwatch: Colors.blue, primarySwatch: m.Colors.blue,
accentColor: Colors.blue.shade700, accentColor: m.Colors.blue.shade700,
), ),
useMaterial3: true, useMaterial3: true,
// fontFamily: 'Atkinson Hyperlegible', // fontFamily: 'Atkinson Hyperlegible',
), ),
routes: routes, routes: routes,
); );
// } }
} }
} }

3
lib/models/ui_design.dart

@ -1,6 +1,7 @@
enum UiDesign { enum UiDesign {
MATERIAL, MATERIAL,
CUPERTINO CUPERTINO,
FLUENT,
} }
class UnmatchedUiDesignException implements Exception { class UnmatchedUiDesignException implements Exception {

3
lib/pages/main/main_page.dart

@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/about/about_page.dart'; import 'package:info_tren/pages/about/about_page.dart';
import 'package:info_tren/pages/main/main_page_cupertino.dart'; import 'package:info_tren/pages/main/main_page_cupertino.dart';
import 'package:info_tren/pages/main/main_page_fluent.dart';
import 'package:info_tren/pages/main/main_page_material.dart'; import 'package:info_tren/pages/main/main_page_material.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
@ -20,6 +21,8 @@ class MainPage extends ConsumerWidget {
return const MainPageMaterial(); return const MainPageMaterial();
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return const MainPageCupertino(); return const MainPageCupertino();
case UiDesign.FLUENT:
return const MainPageFluent();
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }

73
lib/pages/main/main_page_fluent.dart

@ -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(),
),
),
),
);
}
}

3
lib/pages/station_arrdep_page/select_station/select_station.dart

@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_cupertino.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_cupertino.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_fluent.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_material.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_material.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -20,6 +21,8 @@ class SelectStationPage extends ConsumerWidget {
return const SelectStationPageMaterial(); return const SelectStationPageMaterial();
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return const SelectStationPageCupertino(); return const SelectStationPageCupertino();
case UiDesign.FLUENT:
return const SelectStationPageFluent();
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }

59
lib/pages/station_arrdep_page/select_station/select_station_fluent.dart

@ -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,
),
),
],
),
),
);
}
}

6
lib/pages/station_arrdep_page/view_station/view_station.dart

@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/components/refresh_future_builder.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_cupertino.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_cupertino.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_fluent.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_material.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_material.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.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -31,6 +32,11 @@ class ViewStationPage extends HookConsumerWidget {
tab: tab.value, tab: tab.value,
setTab: (newTab) => tab.value = newTab, setTab: (newTab) => tab.value = newTab,
); );
case UiDesign.FLUENT:
return ViewStationPageFluent(
tab: tab.value,
setTab: (newTab) => tab.value = newTab,
);
} }
} }
} }

267
lib/pages/station_arrdep_page/view_station/view_station_fluent.dart

@ -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);
}
}

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

@ -2,7 +2,7 @@ import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/badge.dart'; import 'package:info_tren/components/badge/badge.dart';
import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/loading/loading.dart';
import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/components/refresh_future_builder.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
@ -205,7 +205,7 @@ class ViewStationPageMaterial extends ViewStationPageShared {
IntrinsicHeight( IntrinsicHeight(
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: MaterialBadge( child: Badge(
text: item.status.platform!, text: item.status.platform!,
caption: 'Linia', caption: 'Linia',
isOnTime: item.status.real && item.status.delay <= 0, isOnTime: item.status.real && item.status.delay <= 0,

9
lib/pages/train_info_page/select_train/select_train.dart

@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train_cupertino.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train_cupertino.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train_fluent.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train_material.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train_material.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.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -26,6 +27,8 @@ class SelectTrainPage extends ConsumerWidget {
return const SelectTrainPageMaterial(); return const SelectTrainPageMaterial();
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return const SelectTrainPageCupertino(); return const SelectTrainPageCupertino();
case UiDesign.FLUENT:
return const SelectTrainPageFluent();
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
@ -70,7 +73,11 @@ abstract class SelectTrainPageState extends State<SelectTrainPageShared> {
return posInNum1.compareTo(posInNum2); return posInNum1.compareTo(posInNum2);
} }
return t1.number.length.compareTo(t2.number.length); if (t1.number.length != t2.number.length) {
return t1.number.length.compareTo(t2.number.length);
}
return t1.number.compareTo(t2.number);
}); });
return filtered; return filtered;

46
lib/pages/train_info_page/select_train/select_train_fluent.dart

@ -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,
),
],
),
),
);
}
}

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

@ -1,12 +1,11 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/api/train_data.dart'; import 'package:info_tren/api/train_data.dart';
import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/loading/loading.dart';
import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/components/refresh_future_builder.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_fluent.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -30,34 +29,35 @@ class TrainInfo extends ConsumerWidget {
replaceFutureBuilder(() => getTrain(trainNumber, date: DateTime.now().subtract(const Duration(days: 1)))); replaceFutureBuilder(() => getTrain(trainNumber, date: DateTime.now().subtract(const Duration(days: 1))));
} }
if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) {
return TrainInfoLoading(title: trainNumber.toString(), loadingText: "Se încarcă...",);
}
else if (snapshot.state == RefreshFutureBuilderState.error) {
return TrainInfoError(title: '$trainNumber - Error', error: snapshot.error!, refresh: refresh,);
}
switch (uiDesign) { switch (uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) {
return TrainInfoLoadingMaterial(title: trainNumber.toString(), loadingText: "Se încarcă...",);
}
else if (snapshot.state == RefreshFutureBuilderState.error) {
return TrainInfoErrorMaterial(title: '$trainNumber - Error', error: snapshot.error!, refresh: refresh,);
}
return TrainInfoMaterial( return TrainInfoMaterial(
trainData: snapshot.data!, trainData: snapshot.data!,
refresh: refresh, refresh: refresh,
isRefreshing: snapshot.state == RefreshFutureBuilderState.refreshing,
onViewYesterdayTrain: onViewYesterdayTrain, onViewYesterdayTrain: onViewYesterdayTrain,
); );
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) {
return TrainInfoLoadingCupertino(title: trainNumber.toString(), loadingText: "Se încarcă...",);
}
else if (snapshot.state == RefreshFutureBuilderState.error) {
return TrainInfoErrorCupertino(title: '$trainNumber - Error', error: snapshot.error!, refresh: refresh,);
}
return TrainInfoCupertino( return TrainInfoCupertino(
trainData: snapshot.data!, trainData: snapshot.data!,
refresh: refresh, refresh: refresh,
isRefreshing: snapshot.state == RefreshFutureBuilderState.refreshing, isRefreshing: snapshot.state == RefreshFutureBuilderState.refreshing,
onViewYesterdayTrain: onViewYesterdayTrain, onViewYesterdayTrain: onViewYesterdayTrain,
); );
case UiDesign.FLUENT:
return TrainInfoFluent(
trainData: snapshot.data!,
refresh: refresh,
isRefreshing: snapshot.state == RefreshFutureBuilderState.refreshing,
onViewYesterdayTrain: onViewYesterdayTrain,
);
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
@ -73,23 +73,176 @@ class TrainInfoArguments {
TrainInfoArguments({required this.trainNumber, this.date}); TrainInfoArguments({required this.trainNumber, this.date});
} }
abstract class TrainInfoLoading extends StatelessWidget { abstract class TrainInfoShared extends StatelessWidget {
final TrainData trainData;
final Future Function()? refresh;
final void Function()? onViewYesterdayTrain;
final bool? isRefreshing;
const TrainInfoShared({
super.key,
required this.trainData,
this.refresh,
this.onViewYesterdayTrain,
this.isRefreshing,
});
}
class TrainInfoLoading extends ConsumerWidget {
final String title;
final String? loadingText;
const TrainInfoLoading({
super.key,
required this.title,
this.loadingText,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider);
switch (uiDesign) {
case UiDesign.MATERIAL:
return TrainInfoLoadingMaterial(
title: title,
loadingText: loadingText,
);
case UiDesign.CUPERTINO:
return TrainInfoLoadingCupertino(
title: title,
loadingText: loadingText,
);
case UiDesign.FLUENT:
return TrainInfoLoadingFluent(
title: title,
loadingText: loadingText,
);
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}
abstract class TrainInfoLoadingShared extends StatelessWidget {
final String title; final String title;
final Widget loadingWidget; final Widget loadingWidget;
TrainInfoLoading({ TrainInfoLoadingShared({
required this.title, required this.title,
String? loadingText, String? loadingText,
super.key, super.key,
}) : loadingWidget = Loading(text: loadingText,); }) : loadingWidget = Loading(text: loadingText,);
} }
abstract class TrainInfoError extends StatelessWidget { class TrainInfoError extends ConsumerWidget {
final String title;
final Object error;
final Future Function()? refresh;
const TrainInfoError({
super.key,
required this.title,
required this.error,
this.refresh,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider);
switch (uiDesign) {
case UiDesign.MATERIAL:
return TrainInfoErrorMaterial(
title: title,
error: error,
refresh: refresh,
);
case UiDesign.CUPERTINO:
return TrainInfoErrorCupertino(
title: title,
error: error,
refresh: refresh,
);
case UiDesign.FLUENT:
return TrainInfoErrorFluent(
title: title,
error: error,
refresh: refresh,
);
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}
abstract class TrainInfoErrorShared extends StatelessWidget {
final String title; final String title;
final Object error; final Object error;
final Future Function()? refresh; final Future Function()? refresh;
const TrainInfoError({required this.title, required this.error, this.refresh, super.key,}); const TrainInfoErrorShared({required this.title, required this.error, this.refresh, super.key,});
}
class TrainInfoBody extends ConsumerWidget {
final TrainData trainData;
final void Function()? onViewYesterdayTrain;
final Future Function()? refresh;
final bool? isRefreshing;
const TrainInfoBody({
required this.trainData,
this.onViewYesterdayTrain,
this.refresh,
this.isRefreshing,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider);
switch (uiDesign) {
case UiDesign.MATERIAL:
return TrainInfoBodyMaterial(
trainData: trainData,
onViewYesterdayTrain: onViewYesterdayTrain,
refresh: refresh,
isRefreshing: isRefreshing,
);
case UiDesign.CUPERTINO:
return TrainInfoBodyCupertino(
trainData: trainData,
onViewYesterdayTrain: onViewYesterdayTrain,
refresh: refresh,
isRefreshing: isRefreshing,
);
case UiDesign.FLUENT:
return TrainInfoBodyFluent(
trainData: trainData,
onViewYesterdayTrain: onViewYesterdayTrain,
refresh: refresh,
isRefreshing: isRefreshing,
);
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}
abstract class TrainInfoBodyShared extends StatelessWidget {
final TrainData trainData;
final void Function()? onViewYesterdayTrain;
final Future Function()? refresh;
final bool? isRefreshing;
const TrainInfoBodyShared({
required this.trainData,
this.onViewYesterdayTrain,
this.refresh,
this.isRefreshing,
super.key,
});
} }
abstract class DisplayTrainYesterdayWarningCommon extends StatelessWidget { abstract class DisplayTrainYesterdayWarningCommon extends StatelessWidget {

853
lib/pages/train_info_page/view_train/train_info_cupertino.dart

@ -10,7 +10,7 @@ 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_cupertino_DisplayTrainStation.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart';
import 'package:info_tren/utils/state_to_string.dart'; import 'package:info_tren/utils/state_to_string.dart';
class TrainInfoLoadingCupertino extends TrainInfoLoading { class TrainInfoLoadingCupertino extends TrainInfoLoadingShared {
TrainInfoLoadingCupertino({required super.title, super.loadingText, super.key,}); TrainInfoLoadingCupertino({required super.title, super.loadingText, super.key,});
@override @override
@ -26,17 +26,13 @@ class TrainInfoLoadingCupertino extends TrainInfoLoading {
} }
} }
class TrainInfoErrorCupertino extends TrainInfoError { class TrainInfoErrorCupertino extends TrainInfoErrorShared {
const TrainInfoErrorCupertino({ const TrainInfoErrorCupertino({
required Object error, required super.error,
required String title, required super.title,
Future Function()? refresh, super.refresh,
super.key, super.key,
}) : super( });
error: error,
title: title,
refresh: refresh,
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -64,17 +60,12 @@ class TrainInfoErrorCupertino extends TrainInfoError {
} }
} }
class TrainInfoCupertino extends StatelessWidget { class TrainInfoCupertino extends TrainInfoShared {
final TrainData trainData;
final Future Function()? refresh;
final bool? isRefreshing;
final void Function()? onViewYesterdayTrain;
const TrainInfoCupertino({ const TrainInfoCupertino({
required this.trainData, required super.trainData,
this.refresh, super.refresh,
this.isRefreshing, super.isRefreshing,
this.onViewYesterdayTrain, super.onViewYesterdayTrain,
super.key, super.key,
}); });
@ -93,195 +84,10 @@ class TrainInfoCupertino extends StatelessWidget {
child: SafeArea( child: SafeArea(
top: false, top: false,
bottom: false, bottom: false,
child: Builder(builder: (context) { child: TrainInfoBody(
final topPadding = MediaQuery.of(context).padding.top; trainData: trainData,
onViewYesterdayTrain: onViewYesterdayTrain,
return NestedScrollView( ),
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
// SliverPadding(
// padding: EdgeInsets.only(
// top: topPadding,
// ),
// ),
SliverPersistentHeaderPadding(
maxHeight: topPadding,
)
];
},
body: Builder(builder: (context) {
return CustomScrollView(
slivers: <Widget>[
if (refresh != null)
CupertinoSliverRefreshControl(
builder: (context, mode, pulledExtent,
refreshTriggerPullDistance, refreshIndicatorExtent) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: pulledExtent,
child: Column(
children: [
SizedBox(
height: min(
refreshIndicatorExtent, pulledExtent),
child: Center(
child: Builder(
builder: (context) {
if (mode ==
RefreshIndicatorMode.inactive) {
return Container();
} else if (mode ==
RefreshIndicatorMode.done) {
return const Text('Refreshed!');
} else if (mode ==
RefreshIndicatorMode.drag) {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(
animating: false,
),
Text('Pull to refresh...'),
],
);
} else if (mode ==
RefreshIndicatorMode.armed) {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(
animating: false,
),
Text('Release to refresh...'),
],
);
} else {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(),
Text('Refreshing'),
],
);
}
},
),
),
),
Expanded(
child: Container(),
),
],
),
),
],
);
},
onRefresh: refresh,
),
DisplayTrainID(
trainData: trainData,
),
DisplayTrainOperator(
trainData: trainData,
),
DisplayTrainRoute(
trainData: trainData,
),
DisplayTrainDeparture(
trainData: trainData,
),
const SliverToBoxAdapter(
child: CupertinoDivider(
color: foregroundWhite,
),
),
DisplayTrainLastInfo(
trainData: trainData,
),
const SliverToBoxAdapter(
child: CupertinoDivider(),
),
SliverToBoxAdapter(
child: IntrinsicHeight(
child: Row(
children: <Widget>[
// Expanded(
// child: DisplayTrainNextStop(trainData: trainData,),
// ),
Expanded(
child: DisplayTrainRouteDuration(
trainData: trainData,
),
),
// Expanded(
// child: DisplayTrainDestination(trainData: trainData,),
// ),
const SizedBox(
height: double.infinity,
child: CupertinoVerticalDivider(),
),
Expanded(
child: DisplayTrainRouteDistance(
trainData: trainData,
),
),
],
),
),
),
// SliverToBoxAdapter(
// child: CupertinoDivider(),
// ),
// SliverToBoxAdapter(
// child: IntrinsicHeight(
// child: Row(
// children: <Widget>[
// // Expanded(
// // child: DisplayTrainRouteDuration(trainData: trainData,),
// // ),
// Expanded(child: Container(),),
// SizedBox(
// height: double.infinity,
// child: CupertinoVerticalDivider(),
// ),
// Expanded(
// child: DisplayTrainRouteDistance(trainData: trainData,),
// )
// ],
// ),
// ),
// ),
const SliverToBoxAdapter(
child: CupertinoDivider(
color: foregroundWhite,
),
),
if (onViewYesterdayTrain != null && trainData.stations.first.departure!.scheduleTime.compareTo(DateTime.now()) > 0) ...[
SliverToBoxAdapter(
child: DisplayTrainYesterdayWarningCupertino(onViewYesterdayTrain!),
),
const SliverToBoxAdapter(
child: CupertinoDivider(
color: foregroundWhite,
),
),
],
DisplayTrainStations(
trainData: trainData,
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
);
}),
);
}),
), ),
); );
@ -390,31 +196,382 @@ class TrainInfoCupertino extends StatelessWidget {
} }
} }
class TrainInfoBodyCupertino extends TrainInfoBodyShared {
const TrainInfoBodyCupertino({
super.key,
required super.trainData,
super.onViewYesterdayTrain,
super.isRefreshing,
super.refresh,
});
@override
Widget build(BuildContext context) {
final mq = MediaQuery.of(context);
final topPadding = mq.padding.top;
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),
DisplayTrainRoute(trainData: trainData),
DisplayTrainDeparture(trainData: trainData),
const CupertinoDivider(
color: foregroundWhite,
),
DisplayTrainLastInfo(trainData: trainData),
const CupertinoDivider(),
IntrinsicHeight(
child: Row(
children: <Widget>[
// Expanded(
// child: DisplayTrainNextStop(trainData: trainData,),
// ),
Expanded(
child: DisplayTrainRouteDuration(
trainData: trainData,
),
),
// Expanded(
// child: DisplayTrainDestination(trainData: trainData,),
// ),
const SizedBox(
height: double.infinity,
child: CupertinoVerticalDivider(),
),
Expanded(
child: DisplayTrainRouteDistance(
trainData: trainData,
),
),
],
),
),
const CupertinoDivider(
color: foregroundWhite,
),
if (onViewYesterdayTrain != null && trainData.stations.first.departure!.scheduleTime.compareTo(DateTime.now()) > 0) ...[
DisplayTrainYesterdayWarningCupertino(onViewYesterdayTrain!),
const CupertinoDivider(
color: foregroundWhite,
),
],
],
),
),
Expanded(
child: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
// SliverPadding(
// padding: EdgeInsets.only(
// top: topPadding,
// ),
// ),
SliverPersistentHeaderPadding(
maxHeight: topPadding,
)
];
},
body: CustomScrollView(
slivers: [
if (refresh != null)
CupertinoSliverRefreshControl(
builder: (context, mode, pulledExtent,
refreshTriggerPullDistance, refreshIndicatorExtent) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: pulledExtent,
child: Column(
children: [
SizedBox(
height: min(
refreshIndicatorExtent, pulledExtent),
child: Center(
child: Builder(
builder: (context) {
if (mode ==
RefreshIndicatorMode.inactive) {
return Container();
} else if (mode ==
RefreshIndicatorMode.done) {
return const Text('Refreshed!');
} else if (mode ==
RefreshIndicatorMode.drag) {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(
animating: false,
),
Text('Pull to refresh...'),
],
);
} else if (mode ==
RefreshIndicatorMode.armed) {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(
animating: false,
),
Text('Release to refresh...'),
],
);
} else {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(),
Text('Refreshing'),
],
);
}
},
),
),
),
Expanded(
child: Container(),
),
],
),
),
],
);
},
onRefresh: refresh,
),
DisplayTrainStations(
trainData: trainData,
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
),
),
),
],
);
}
return NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
// SliverPadding(
// padding: EdgeInsets.only(
// top: topPadding,
// ),
// ),
SliverPersistentHeaderPadding(
maxHeight: topPadding,
)
];
},
body: Builder(builder: (context) {
return CustomScrollView(
slivers: <Widget>[
if (refresh != null)
CupertinoSliverRefreshControl(
builder: (context, mode, pulledExtent,
refreshTriggerPullDistance, refreshIndicatorExtent) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: pulledExtent,
child: Column(
children: [
SizedBox(
height: min(
refreshIndicatorExtent, pulledExtent),
child: Center(
child: Builder(
builder: (context) {
if (mode ==
RefreshIndicatorMode.inactive) {
return Container();
} else if (mode ==
RefreshIndicatorMode.done) {
return const Text('Refreshed!');
} else if (mode ==
RefreshIndicatorMode.drag) {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(
animating: false,
),
Text('Pull to refresh...'),
],
);
} else if (mode ==
RefreshIndicatorMode.armed) {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(
animating: false,
),
Text('Release to refresh...'),
],
);
} else {
return Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(),
Text('Refreshing'),
],
);
}
},
),
),
),
Expanded(
child: Container(),
),
],
),
),
],
);
},
onRefresh: refresh,
),
...[
DisplayTrainID(
trainData: trainData,
),
DisplayTrainOperator(
trainData: trainData,
),
DisplayTrainRoute(
trainData: trainData,
),
DisplayTrainDeparture(
trainData: trainData,
),
const CupertinoDivider(
color: foregroundWhite,
),
DisplayTrainLastInfo(
trainData: trainData,
),
const CupertinoDivider(),
IntrinsicHeight(
child: Row(
children: <Widget>[
// Expanded(
// child: DisplayTrainNextStop(trainData: trainData,),
// ),
Expanded(
child: DisplayTrainRouteDuration(
trainData: trainData,
),
),
// Expanded(
// child: DisplayTrainDestination(trainData: trainData,),
// ),
const SizedBox(
height: double.infinity,
child: CupertinoVerticalDivider(),
),
Expanded(
child: DisplayTrainRouteDistance(
trainData: trainData,
),
),
],
),
),
const CupertinoDivider(
color: foregroundWhite,
),
if (onViewYesterdayTrain != null && trainData.stations.first.departure!.scheduleTime.compareTo(DateTime.now()) > 0) ...[
DisplayTrainYesterdayWarningCupertino(onViewYesterdayTrain!),
const CupertinoDivider(
color: foregroundWhite,
),
],
].map((e) => SliverToBoxAdapter(child: e)),
// SliverToBoxAdapter(
// child: CupertinoDivider(),
// ),
// SliverToBoxAdapter(
// child: IntrinsicHeight(
// child: Row(
// children: <Widget>[
// // Expanded(
// // child: DisplayTrainRouteDuration(trainData: trainData,),
// // ),
// Expanded(child: Container(),),
// SizedBox(
// height: double.infinity,
// child: CupertinoVerticalDivider(),
// ),
// Expanded(
// child: DisplayTrainRouteDistance(trainData: trainData,),
// )
// ],
// ),
// ),
// ),
DisplayTrainStations(
trainData: trainData,
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
);
}),
);
}
}
class DisplayTrainID extends StatelessWidget { class DisplayTrainID extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainID({required this.trainData, super.key,}); const DisplayTrainID({required this.trainData, super.key,});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverToBoxAdapter( return Center(
child: Center( child: Padding(
child: Padding( padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0), child: Text.rich(
child: Text.rich( TextSpan(
TextSpan( children: [
children: [ TextSpan(
TextSpan( text: trainData.rank,
text: trainData.rank, style: TextStyle(
style: TextStyle( color: trainData.rank.startsWith('IR') ? const Color.fromARGB(255, 255, 0, 0) : null,
color: trainData.rank.startsWith('IR') ? const Color.fromARGB(255, 255, 0, 0) : null,
),
), ),
const TextSpan(text: ' '), ),
TextSpan(text: trainData.number,), const TextSpan(text: ' '),
], TextSpan(text: trainData.number,),
), ],
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle, ),
), style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
), ),
), ),
); );
@ -428,41 +585,39 @@ class DisplayTrainRoute extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverToBoxAdapter( return Row(
child: Row( children: <Widget>[
children: <Widget>[ Expanded(
Expanded( child: Center(
child: Center( child: Padding(
child: Padding( padding: const EdgeInsets.all(4),
padding: const EdgeInsets.all(4), child: Text(
child: Text( trainData.route.from,
trainData.route.from, style:
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
CupertinoTheme.of(context).textTheme.textStyle.copyWith( fontSize: 16,
fontSize: 16, ),
),
),
), ),
), ),
), ),
const Center(child: Text("-")), ),
Expanded( const Center(child: Text("-")),
child: Center( Expanded(
child: Padding( child: Center(
padding: const EdgeInsets.all(4), child: Padding(
child: Text( padding: const EdgeInsets.all(4),
trainData.route.to, child: Text(
style: trainData.route.to,
CupertinoTheme.of(context).textTheme.textStyle.copyWith( style:
fontSize: 16, CupertinoTheme.of(context).textTheme.textStyle.copyWith(
), fontSize: 16,
textAlign: TextAlign.right, ),
), textAlign: TextAlign.right,
), ),
), ),
), ),
], ),
), ],
); );
} }
} }
@ -474,15 +629,13 @@ class DisplayTrainOperator extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverToBoxAdapter( return Center(
child: Center( child: Text(
child: Text( trainData.operator,
trainData.operator, style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( fontSize: 14,
fontSize: 14, fontStyle: FontStyle.italic,
fontStyle: FontStyle.italic, ),
),
),
), ),
); );
} }
@ -495,18 +648,16 @@ class DisplayTrainDeparture extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverToBoxAdapter( return Padding(
child: Padding( padding: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2), child: Text(
child: Text( // "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}",
// "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}", "Plecare în ${trainData.date}",
"Plecare în ${trainData.date}", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( fontStyle: FontStyle.italic,
fontStyle: FontStyle.italic, fontWeight: FontWeight.w200,
fontWeight: FontWeight.w200, ),
), textAlign: TextAlign.center,
textAlign: TextAlign.center,
),
), ),
); );
} }
@ -520,90 +671,86 @@ class DisplayTrainLastInfo extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (trainData.status == null) { if (trainData.status == null) {
return SliverToBoxAdapter( return Container();
child: Container(),
);
} }
return SliverToBoxAdapter( return Column(
child: Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: <Widget>[
children: <Widget>[ Center(
Center( child: Padding(
child: Padding( padding: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2), child: Text(
child: Text( "Ultima informație",
"Ultima informație", style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( fontSize: 20,
fontSize: 20, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, ),
),
),
), ),
), ),
Row( ),
children: <Widget>[ Row(
Padding( children: <Widget>[
padding: const EdgeInsets.all(4), Padding(
child: Text( padding: const EdgeInsets.all(4),
trainData.status!.station, child: Text(
style: CupertinoTheme.of(context).textTheme.textStyle, trainData.status!.station,
textAlign: TextAlign.left, style: CupertinoTheme.of(context).textTheme.textStyle,
), textAlign: TextAlign.left,
),
Expanded(
child: Container(),
), ),
Padding( ),
padding: const EdgeInsets.all(4), Expanded(
child: Text( child: Container(),
stateToString(trainData.status!.state), ),
style: CupertinoTheme.of(context).textTheme.textStyle, Padding(
textAlign: TextAlign.right, padding: const EdgeInsets.all(4),
), child: Text(
stateToString(trainData.status!.state),
style: CupertinoTheme.of(context).textTheme.textStyle,
textAlign: TextAlign.right,
), ),
], ),
), ],
// FutureDisplay<DateTime>( ),
// future: trainData.lastInfo.dateAndTime, // FutureDisplay<DateTime>(
// builder: (context, dt) { // future: trainData.lastInfo.dateAndTime,
// return Text( // builder: (context, dt) {
// "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}", // return Text(
// textAlign: TextAlign.center, // "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}",
// ); // textAlign: TextAlign.center,
// }, // );
// ), // },
Builder( // ),
builder: (context) { Builder(
final data = trainData.status!.delay; builder: (context) {
final data = trainData.status!.delay;
if (data == 0) { if (data == 0) {
return Container(); return Container();
} }
if (data > 0) { if (data > 0) {
return Text( return Text(
"$data ${data == 1 ? 'minut' : 'minute'} întârziere", "$data ${data == 1 ? 'minut' : 'minute'} întârziere",
style: style:
CupertinoTheme.of(context).textTheme.textStyle.copyWith( CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 14, fontSize: 14,
color: CupertinoColors.destructiveRed, color: CupertinoColors.destructiveRed,
), ),
); );
} else { } else {
return Text( return Text(
"${-data} ${data == -1 ? 'minut' : 'minute'} mai devreme", "${-data} ${data == -1 ? 'minut' : 'minute'} mai devreme",
style: style:
CupertinoTheme.of(context).textTheme.textStyle.copyWith( CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 12, fontSize: 12,
color: CupertinoColors.systemGreen, color: CupertinoColors.systemGreen,
), ),
); );
} }
}, },
) )
], ],
),
); );
} }
} }

6
lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart

@ -1,5 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:info_tren/components/badge.dart'; import 'package:info_tren/components/badge/badge.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
class DisplayTrainStation extends StatelessWidget { class DisplayTrainStation extends StatelessWidget {
@ -47,7 +47,7 @@ class DisplayTrainStation extends StatelessWidget {
final isOnTime = delay <= 0 && real == true; final isOnTime = delay <= 0 && real == true;
const isNotScheduled = false; const isNotScheduled = false;
return CupertinoBadge( return Badge(
text: station.km.toString(), text: station.km.toString(),
caption: 'km', caption: 'km',
isNotScheduled: isNotScheduled, isNotScheduled: isNotScheduled,
@ -65,7 +65,7 @@ class DisplayTrainStation extends StatelessWidget {
flex: 1, flex: 1,
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: station.platform == null ? Container() : CupertinoBadge(text: station.platform!, caption: 'linia'), child: station.platform == null ? Container() : Badge(text: station.platform!, caption: 'linia'),
), ),
), ),
], ],

807
lib/pages/train_info_page/view_train/train_info_fluent.dart

@ -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,
),
);
}
}

447
lib/pages/train_info_page/view_train/train_info_fluent_DisplayTrainStation.dart

@ -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();
}
}

328
lib/pages/train_info_page/view_train/train_info_material.dart

@ -7,7 +7,7 @@ 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/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart';
import 'package:info_tren/utils/state_to_string.dart'; import 'package:info_tren/utils/state_to_string.dart';
class TrainInfoLoadingMaterial extends TrainInfoLoading { class TrainInfoLoadingMaterial extends TrainInfoLoadingShared {
TrainInfoLoadingMaterial({required super.title, super.loadingText, super.key,}); TrainInfoLoadingMaterial({required super.title, super.loadingText, super.key,});
@override @override
@ -24,7 +24,7 @@ class TrainInfoLoadingMaterial extends TrainInfoLoading {
} }
} }
class TrainInfoErrorMaterial extends TrainInfoError { class TrainInfoErrorMaterial extends TrainInfoErrorShared {
const TrainInfoErrorMaterial({ const TrainInfoErrorMaterial({
required super.error, required super.error,
required super.title, required super.title,
@ -61,15 +61,12 @@ class TrainInfoErrorMaterial extends TrainInfoError {
bool isSmallScreen(BuildContext context) => bool isSmallScreen(BuildContext context) =>
MediaQuery.of(context).size.height <= 425; MediaQuery.of(context).size.height <= 425;
class TrainInfoMaterial extends StatelessWidget { class TrainInfoMaterial extends TrainInfoShared {
final TrainData trainData;
final Future Function()? refresh;
final void Function()? onViewYesterdayTrain;
const TrainInfoMaterial({ const TrainInfoMaterial({
required this.trainData, required super.trainData,
this.refresh, super.refresh,
this.onViewYesterdayTrain, super.onViewYesterdayTrain,
super.isRefreshing,
super.key, super.key,
}); });
@ -79,12 +76,13 @@ class TrainInfoMaterial extends StatelessWidget {
builder: (context) { builder: (context) {
return Scaffold( return Scaffold(
appBar: isSmallScreen(context) appBar: isSmallScreen(context)
? null ? null
: AppBar( : AppBar(
centerTitle: true, centerTitle: true,
title: Text( title: Text(
"Informații despre ${trainData.rank} ${trainData.number}"), "Informații despre ${trainData.rank} ${trainData.number}",
), ),
),
body: Column( body: Column(
children: <Widget>[ children: <Widget>[
if (isSmallScreen(context)) if (isSmallScreen(context))
@ -93,8 +91,8 @@ class TrainInfoMaterial extends StatelessWidget {
left: false, left: false,
right: false, right: false,
child: SlimAppBar( child: SlimAppBar(
title: title: 'INFO TREN - ${trainData.rank} ${trainData.number}',
'INFO TREN - ${trainData.rank} ${trainData.number}'), ),
), ),
Expanded( Expanded(
child: SafeArea( child: SafeArea(
@ -102,102 +100,9 @@ class TrainInfoMaterial extends StatelessWidget {
top: isSmallScreen(context) ? false : true, top: isSmallScreen(context) ? false : true,
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: refresh ?? () async {}, onRefresh: refresh ?? () async {},
child: CustomScrollView( child: TrainInfoBody(
slivers: <Widget>[ trainData: trainData,
SliverToBoxAdapter( onViewYesterdayTrain: onViewYesterdayTrain,
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: Divider(
// color: Colors.white70,
// height: isSmallScreen(context) ? 8 : 16,
// ),
// ),
SliverToBoxAdapter(
child: DisplayTrainLastInfo(
trainData: trainData,
),
),
SliverToBoxAdapter(
child: IntrinsicHeight(
child: Row(
children: <Widget>[
// Expanded(child: DisplayTrainNextStop(trainData: trainData,)),
// Expanded(child: DisplayTrainDestination(trainData: trainData,)),
Expanded(
child: DisplayTrainRouteDuration(
trainData: trainData,
)),
Expanded(
child: DisplayTrainRouteDistance(
trainData: trainData,
),
),
],
),
),
),
// SliverToBoxAdapter(
// child: IntrinsicHeight(
// child: Row(
// children: <Widget>[
// // Expanded(child: DisplayTrainRouteDuration(trainData: trainData,)),
// Expanded(child: Container(),),
// Expanded(child: DisplayTrainRouteDistance(trainData: trainData,)),
// ],
// ),
// ),
// ),
SliverToBoxAdapter(
child: Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
),
if (onViewYesterdayTrain != null &&
trainData.stations.first.departure!.scheduleTime
.compareTo(DateTime.now()) >
0) ...[
SliverToBoxAdapter(
child: DisplayTrainYesterdayWarningMaterial(
onViewYesterdayTrain!),
),
SliverToBoxAdapter(
child: Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
),
],
DisplayTrainStations(
trainData: trainData,
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
), ),
), ),
), ),
@ -210,6 +115,201 @@ class TrainInfoMaterial extends StatelessWidget {
} }
} }
class TrainInfoBodyMaterial extends TrainInfoBodyShared {
const TrainInfoBodyMaterial({
super.key,
required super.trainData,
super.onViewYesterdayTrain,
super.isRefreshing,
super.refresh,
});
@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),
DisplayTrainLastInfo(trainData: trainData),
IntrinsicHeight(
child: Row(
children: <Widget>[
Expanded(
child: DisplayTrainRouteDuration(
trainData: trainData,
),
),
Expanded(
child: DisplayTrainRouteDistance(
trainData: trainData,
),
),
],
),
),
Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
if (onViewYesterdayTrain != null &&
trainData.stations.first.departure!.scheduleTime
.compareTo(DateTime.now()) >
0)
...[
DisplayTrainYesterdayWarningMaterial(
onViewYesterdayTrain!,
),
Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
],
],
),
),
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: Divider(
// color: Colors.white70,
// height: isSmallScreen(context) ? 8 : 16,
// ),
// ),
SliverToBoxAdapter(
child: DisplayTrainLastInfo(
trainData: trainData,
),
),
SliverToBoxAdapter(
child: IntrinsicHeight(
child: Row(
children: <Widget>[
// Expanded(child: DisplayTrainNextStop(trainData: trainData,)),
// Expanded(child: DisplayTrainDestination(trainData: trainData,)),
Expanded(
child: DisplayTrainRouteDuration(
trainData: trainData,
),
),
Expanded(
child: DisplayTrainRouteDistance(
trainData: trainData,
),
),
],
),
),
),
// SliverToBoxAdapter(
// child: IntrinsicHeight(
// child: Row(
// children: <Widget>[
// // Expanded(child: DisplayTrainRouteDuration(trainData: trainData,)),
// Expanded(child: Container(),),
// Expanded(child: DisplayTrainRouteDistance(trainData: trainData,)),
// ],
// ),
// ),
// ),
SliverToBoxAdapter(
child: Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
),
if (onViewYesterdayTrain != null &&
trainData.stations.first.departure!.scheduleTime
.compareTo(DateTime.now()) >
0) ...[
SliverToBoxAdapter(
child: DisplayTrainYesterdayWarningMaterial(
onViewYesterdayTrain!),
),
SliverToBoxAdapter(
child: Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
),
],
DisplayTrainStations(
trainData: trainData,
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery
.of(context)
.viewPadding
.bottom,
),
),
],
);
}
}
}
class DisplayTrainID extends StatelessWidget { class DisplayTrainID extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
@ -798,7 +898,7 @@ class DisplayTrainStations extends StatelessWidget {
onTap: () { onTap: () {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
ViewStationPage.routeName, ViewStationPage.routeName,
arguments: trainData.stations[index].name, arguments: ViewStationArguments(stationName: trainData.stations[index].name),
); );
}, },
), ),

6
lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/components/badge.dart'; import 'package:info_tren/components/badge/badge.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart';
class DisplayTrainStation extends StatelessWidget { class DisplayTrainStation extends StatelessWidget {
@ -54,7 +54,7 @@ class DisplayTrainStation extends StatelessWidget {
final isOnTime = delay <= 0 && real == true; final isOnTime = delay <= 0 && real == true;
const isNotScheduled = false; const isNotScheduled = false;
return MaterialBadge( return Badge(
text: station.km.toString(), text: station.km.toString(),
caption: 'km', caption: 'km',
isNotScheduled: isNotScheduled, isNotScheduled: isNotScheduled,
@ -74,7 +74,7 @@ class DisplayTrainStation extends StatelessWidget {
? Container() ? Container()
: Align( : Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: MaterialBadge(text: station.platform!, caption: 'linia',), child: Badge(text: station.platform!, caption: 'linia',),
), ),
), ),
], ],

3
lib/utils/default_ui_design.dart

@ -6,6 +6,9 @@ UiDesign get defaultUiDesign {
if (Platform.isIOS) { if (Platform.isIOS) {
return UiDesign.CUPERTINO; return UiDesign.CUPERTINO;
} }
else if (Platform.isLinux || Platform.isWindows) {
return UiDesign.FLUENT;
}
else { else {
return UiDesign.MATERIAL; return UiDesign.MATERIAL;
} }

44
pubspec.lock

@ -99,6 +99,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
code_builder: code_builder:
dependency: transitive dependency: transitive
description: description:
@ -162,6 +169,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
fluent_ui:
dependency: "direct main"
description:
name: fluent_ui
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.3+1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -181,6 +195,11 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_riverpod: flutter_riverpod:
dependency: transitive dependency: transitive
description: description:
@ -256,6 +275,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
intl:
dependency: transitive
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -311,7 +337,7 @@ packages:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.1.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -424,6 +450,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
recase:
dependency: transitive
description:
name: recase
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
riverpod: riverpod:
dependency: transitive dependency: transitive
description: description:
@ -431,6 +464,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.0.2"
scroll_pos:
dependency: transitive
description:
name: scroll_pos
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -659,7 +699,7 @@ packages:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.4" version: "2.1.2"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:

3
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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 2.7.8 version: 2.7.9
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"
@ -33,6 +33,7 @@ dependencies:
freezed_annotation: ^2.2.0 freezed_annotation: ^2.2.0
json_annotation: ^4.7.0 json_annotation: ^4.7.0
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
fluent_ui: ^4.0.3+1
dev_dependencies: dev_dependencies:
# flutter_test: # flutter_test:

Loading…
Cancel
Save