@ -0,0 +1,29 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to |
||||
# check for errors, warnings, and lints. |
||||
# |
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled |
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be |
||||
# invoked from the command line by running `flutter analyze`. |
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps, |
||||
# packages, and plugins designed to encourage good coding practices. |
||||
include: package:flutter_lints/flutter.yaml |
||||
|
||||
linter: |
||||
# The lint rules applied to this project can be customized in the |
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml` |
||||
# included above or to enable additional rules. A list of all available lints |
||||
# and their documentation is published at |
||||
# https://dart-lang.github.io/linter/lints/index.html. |
||||
# |
||||
# Instead of disabling a lint rule for the entire project in the |
||||
# section below, it can also be suppressed for a single line of code |
||||
# or a specific dart file by using the `// ignore: name_of_lint` and |
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file |
||||
# producing the lint. |
||||
rules: |
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule |
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule |
||||
|
||||
# Additional information about this file can be found at |
||||
# https://dart.dev/guides/language/analysis-options |
@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar |
||||
/.gradle |
||||
/captures/ |
||||
/gradlew |
||||
/gradlew.bat |
||||
/local.properties |
||||
GeneratedPluginRegistrant.java |
||||
|
||||
# Remember to never publicly share your keystore. |
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app |
||||
key.properties |
||||
**/*.keystore |
||||
**/*.jks |
@ -1,4 +1,4 @@
|
||||
package ml.dandevelop.info_tren |
||||
package xyz.dcdevelop.info_tren |
||||
|
||||
import io.flutter.embedding.android.FlutterActivity |
||||
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<!-- Modify this file to customize your launch splash screen --> |
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<item android:drawable="?android:colorBackground" /> |
||||
|
||||
<!-- You can insert your own image assets here --> |
||||
<!-- <item> |
||||
<bitmap |
||||
android:gravity="center" |
||||
android:src="@mipmap/launch_image" /> |
||||
</item> --> |
||||
</layer-list> |
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> |
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> |
||||
<!-- Show a splash screen on the activity. Automatically removed when |
||||
Flutter draws its first frame --> |
||||
<item name="android:windowBackground">@drawable/launch_background</item> |
||||
</style> |
||||
<!-- Theme applied to the Android Window as soon as the process has started. |
||||
This theme determines the color of the Android Window while your |
||||
Flutter UI initializes, as well as behind your Flutter UI while its |
||||
running. |
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. --> |
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> |
||||
<item name="android:windowBackground">?android:colorBackground</item> |
||||
</style> |
||||
</resources> |
@ -1,8 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> |
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> |
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> |
||||
<!-- Show a splash screen on the activity. Automatically removed when |
||||
Flutter draws its first frame --> |
||||
<item name="android:windowBackground">@drawable/launch_background</item> |
||||
</style> |
||||
<!-- Theme applied to the Android Window as soon as the process has started. |
||||
This theme determines the color of the Android Window while your |
||||
Flutter UI initializes, as well as behind your Flutter UI while its |
||||
running. |
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. --> |
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> |
||||
<item name="android:windowBackground">?android:colorBackground</item> |
||||
</style> |
||||
</resources> |
||||
|
@ -1,2 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx1536M |
||||
|
||||
android.useAndroidX=true |
||||
android.enableJetifier=true |
||||
|
@ -1,15 +1,11 @@
|
||||
include ':app' |
||||
|
||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() |
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties") |
||||
def properties = new Properties() |
||||
|
||||
def plugins = new Properties() |
||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') |
||||
if (pluginsFile.exists()) { |
||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } |
||||
} |
||||
assert localPropertiesFile.exists() |
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } |
||||
|
||||
plugins.each { name, path -> |
||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() |
||||
include ":$name" |
||||
project(":$name").projectDir = pluginDirectory |
||||
} |
||||
def flutterSdkPath = properties.getProperty("flutter.sdk") |
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" |
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" |
||||
|
@ -0,0 +1,33 @@
|
||||
*.mode1v3 |
||||
*.mode2v3 |
||||
*.moved-aside |
||||
*.pbxuser |
||||
*.perspectivev3 |
||||
**/*sync/ |
||||
.sconsign.dblite |
||||
.tags* |
||||
**/.vagrant/ |
||||
**/DerivedData/ |
||||
Icon? |
||||
**/Pods/ |
||||
**/.symlinks/ |
||||
profile |
||||
xcuserdata |
||||
**/.generated/ |
||||
Flutter/App.framework |
||||
Flutter/Flutter.framework |
||||
Flutter/Flutter.podspec |
||||
Flutter/Generated.xcconfig |
||||
Flutter/ephemeral/ |
||||
Flutter/app.flx |
||||
Flutter/app.zip |
||||
Flutter/flutter_assets/ |
||||
Flutter/flutter_export_environment.sh |
||||
ServiceDefinitions.json |
||||
Runner/GeneratedPluginRegistrant.* |
||||
|
||||
# Exceptions to above rules. |
||||
!default.mode1v3 |
||||
!default.mode2v3 |
||||
!default.pbxuser |
||||
!default.perspectivev3 |
@ -1,2 +1,2 @@
|
||||
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" |
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" |
||||
#include "Generated.xcconfig" |
||||
|
@ -1,18 +0,0 @@
|
||||
# |
||||
# NOTE: This podspec is NOT to be published. It is only used as a local source! |
||||
# This is a generated file; do not edit or check into version control. |
||||
# |
||||
|
||||
Pod::Spec.new do |s| |
||||
s.name = 'Flutter' |
||||
s.version = '1.0.0' |
||||
s.summary = 'High-performance, high-fidelity mobile apps.' |
||||
s.homepage = 'https://flutter.io' |
||||
s.license = { :type => 'MIT' } |
||||
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } |
||||
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } |
||||
s.ios.deployment_target = '8.0' |
||||
# Framework linking is handled by Flutter tooling, not CocoaPods. |
||||
# Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. |
||||
s.vendored_frameworks = 'path/to/nothing' |
||||
end |
@ -1,2 +1,2 @@
|
||||
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" |
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" |
||||
#include "Generated.xcconfig" |
||||
|
@ -1,22 +0,0 @@
|
||||
PODS: |
||||
- Flutter (1.0.0) |
||||
- webview_flutter (0.0.1): |
||||
- Flutter |
||||
|
||||
DEPENDENCIES: |
||||
- Flutter (from `Flutter`) |
||||
- webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) |
||||
|
||||
EXTERNAL SOURCES: |
||||
Flutter: |
||||
:path: Flutter |
||||
webview_flutter: |
||||
:path: ".symlinks/plugins/webview_flutter/ios" |
||||
|
||||
SPEC CHECKSUMS: |
||||
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c |
||||
webview_flutter: 1aa7604e6cdb451a9b7ed2c37d5454c0b440246b |
||||
|
||||
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c |
||||
|
||||
COCOAPODS: 1.10.1 |
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>IDEDidComputeMac32BitWarning</key> |
||||
<true/> |
||||
</dict> |
||||
</plist> |
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>PreviewsEnabled</key> |
||||
<false/> |
||||
</dict> |
||||
</plist> |
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>PreviewsEnabled</key> |
||||
<false/> |
||||
</dict> |
||||
</plist> |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,9 @@
|
||||
import 'package:http/http.dart' as http; |
||||
import 'package:info_tren/models/train_data.dart'; |
||||
|
||||
const AUTHORITY = 'scraper.infotren.dcdevelop.xyz'; |
||||
|
||||
Future<TrainData> getTrain(int trainNumber) async { |
||||
final response = await http.get(Uri.https(AUTHORITY, 'train/$trainNumber')); |
||||
return trainDataFromJson(response.body); |
||||
} |
@ -0,0 +1,60 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; |
||||
|
||||
class CupertinoDivider extends StatelessWidget { |
||||
final Color color; |
||||
|
||||
CupertinoDivider({Key? key, Color? color}): |
||||
color = color ?? FOREGROUND_DARK_GREY, |
||||
super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Container( |
||||
height: 1, |
||||
), |
||||
Container( |
||||
height: 1, |
||||
decoration: BoxDecoration( |
||||
color: color, |
||||
), |
||||
), |
||||
Container( |
||||
height: 1, |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class CupertinoVerticalDivider extends StatelessWidget { |
||||
final Color color; |
||||
|
||||
CupertinoVerticalDivider({Key? key, Color? color}): |
||||
color = color ?? FOREGROUND_DARK_GREY, |
||||
super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Row( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Container( |
||||
width: 1, |
||||
), |
||||
Container( |
||||
width: 1, |
||||
decoration: BoxDecoration( |
||||
color: color, |
||||
), |
||||
), |
||||
Container( |
||||
width: 1, |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,42 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:info_tren/models/ui_design.dart'; |
||||
import 'package:info_tren/utils/default_ui_design.dart'; |
||||
|
||||
class FutureDisplay<T> extends StatelessWidget { |
||||
final UiDesign? uiDesign; |
||||
final Future<T> future; |
||||
final Widget Function<T>(BuildContext context, T data) builder; |
||||
final Widget Function(BuildContext context, Object error, StackTrace? st)? errorBuilder; |
||||
|
||||
FutureDisplay({Key? key, required this.future, required this.builder, this.errorBuilder, this.uiDesign}): super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final uiDesign = this.uiDesign ?? defaultUiDesign; |
||||
return FutureBuilder( |
||||
future: future, |
||||
builder: (context, snapshot) { |
||||
if (snapshot.hasData) return builder(context, snapshot.data); |
||||
if (snapshot.hasError) return (errorBuilder != null ? errorBuilder!(context, snapshot.error!, snapshot.stackTrace) : throw snapshot.error!); |
||||
if (snapshot.connectionState == ConnectionState.done) return Container(); |
||||
|
||||
Widget loadingWidget; |
||||
switch (uiDesign) { |
||||
case UiDesign.MATERIAL: |
||||
loadingWidget = CircularProgressIndicator(); |
||||
break; |
||||
case UiDesign.CUPERTINO: |
||||
loadingWidget = CupertinoActivityIndicator(); |
||||
break; |
||||
default: |
||||
throw UnmatchedUiDesignException(uiDesign); |
||||
} |
||||
|
||||
return Center( |
||||
child: loadingWidget, |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
|
||||
import 'package:flutter/widgets.dart'; |
||||
import 'package:info_tren/components/loading/loading_cupertino.dart'; |
||||
import 'package:info_tren/components/loading/loading_material.dart'; |
||||
import 'package:info_tren/models/ui_design.dart'; |
||||
import 'package:info_tren/utils/default_ui_design.dart'; |
||||
|
||||
class Loading extends StatelessWidget { |
||||
static const DEFAULT_TEXT = 'Loading...'; |
||||
|
||||
final UiDesign? uiDesign; |
||||
final String? text; |
||||
const Loading({ Key? key, this.text, this.uiDesign }) : super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final uiDesign = this.uiDesign ?? defaultUiDesign; |
||||
switch (uiDesign) { |
||||
case UiDesign.MATERIAL: |
||||
return LoadingMaterial(text: text ?? DEFAULT_TEXT,); |
||||
case UiDesign.CUPERTINO: |
||||
return LoadingCupertino(text: text ?? DEFAULT_TEXT,); |
||||
default: |
||||
throw UnmatchedUiDesignException(uiDesign); |
||||
} |
||||
} |
||||
} |
||||
|
||||
abstract class LoadingCommon extends StatelessWidget { |
||||
final String text; |
||||
LoadingCommon({required this.text}); |
||||
} |
@ -0,0 +1,28 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:flutter/src/widgets/framework.dart'; |
||||
import 'package:info_tren/components/loading/loading.dart'; |
||||
|
||||
class LoadingCupertino extends LoadingCommon { |
||||
LoadingCupertino({required String text}) : super(text: text,); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Center( |
||||
child: Column( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: CupertinoActivityIndicator(), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Text(text), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:info_tren/components/loading/loading.dart'; |
||||
|
||||
class LoadingMaterial extends LoadingCommon { |
||||
LoadingMaterial({required String text}) : super(text: text,); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Center( |
||||
child: Column( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: CircularProgressIndicator(), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Text(text), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,117 @@
|
||||
import 'package:flutter/widgets.dart'; |
||||
|
||||
class RefreshFutureBuilder<T> extends StatefulWidget { |
||||
final Future<T> Function()? futureCreator; |
||||
final T? initialData; |
||||
final Widget Function(BuildContext context, Future Function() refresh, RefreshFutureBuilderSnapshot<T> snapshot) builder; |
||||
|
||||
const RefreshFutureBuilder({ Key? key, this.futureCreator, this.initialData, required this.builder }) : super(key: key); |
||||
|
||||
@override |
||||
_RefreshFutureBuilderState<T> createState() => _RefreshFutureBuilderState<T>(); |
||||
} |
||||
|
||||
class _RefreshFutureBuilderState<T> extends State<RefreshFutureBuilder<T>> { |
||||
late RefreshFutureBuilderSnapshot<T> snapshot; |
||||
Future<T> Function()? futureCreator; |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
snapshot = widget.initialData != null ? RefreshFutureBuilderSnapshot.initial(widget.initialData!) : RefreshFutureBuilderSnapshot.nothing(); |
||||
} |
||||
|
||||
@override |
||||
void didChangeDependencies() { |
||||
super.didChangeDependencies(); |
||||
if (futureCreator != widget.futureCreator) { |
||||
futureCreator = widget.futureCreator; |
||||
runFuture(); |
||||
} |
||||
} |
||||
|
||||
Future runFuture() async { |
||||
if (futureCreator == null) { |
||||
return; |
||||
} |
||||
// Set state to signify loading |
||||
setState(() { |
||||
switch (snapshot.state) { |
||||
case RefreshFutureBuilderState.none: |
||||
snapshot = RefreshFutureBuilderSnapshot.waiting(); |
||||
break; |
||||
case RefreshFutureBuilderState.initial: |
||||
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data); |
||||
break; |
||||
case RefreshFutureBuilderState.waiting: |
||||
return; |
||||
case RefreshFutureBuilderState.error: |
||||
snapshot = RefreshFutureBuilderSnapshot.refresh(null, snapshot.error, snapshot.stackTrace); |
||||
break; |
||||
case RefreshFutureBuilderState.done: |
||||
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data); |
||||
break; |
||||
case RefreshFutureBuilderState.refreshing: |
||||
return; |
||||
case RefreshFutureBuilderState.refreshError: |
||||
snapshot = RefreshFutureBuilderSnapshot.refresh(null, snapshot.error, snapshot.stackTrace); |
||||
break; |
||||
default: |
||||
} |
||||
}); |
||||
try { |
||||
final data = await futureCreator!(); |
||||
setState(() { |
||||
snapshot = RefreshFutureBuilderSnapshot.withData(data); |
||||
}); |
||||
} |
||||
catch (e, st) { |
||||
setState(() { |
||||
if (snapshot.state == RefreshFutureBuilderState.waiting) { |
||||
snapshot = RefreshFutureBuilderSnapshot.withError(e, st); |
||||
} |
||||
else { |
||||
snapshot = RefreshFutureBuilderSnapshot.refreshError(snapshot.data, e, st); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return widget.builder( |
||||
context, |
||||
runFuture, |
||||
snapshot, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class RefreshFutureBuilderSnapshot<T> { |
||||
final RefreshFutureBuilderState state; |
||||
final T? data; |
||||
final Object? error; |
||||
final StackTrace? stackTrace; |
||||
|
||||
bool get hasData => data != null; |
||||
bool get hasError => error != null; |
||||
|
||||
const RefreshFutureBuilderSnapshot._(this.state, this.data, this.error, this.stackTrace); |
||||
const RefreshFutureBuilderSnapshot.nothing() : state = RefreshFutureBuilderState.none, data = null, error = null, stackTrace = null; |
||||
const RefreshFutureBuilderSnapshot.initial(this.data) : state = RefreshFutureBuilderState.initial, error = null, stackTrace = null; |
||||
const RefreshFutureBuilderSnapshot.waiting() : state = RefreshFutureBuilderState.waiting, data = null, error = null, stackTrace = null; |
||||
const RefreshFutureBuilderSnapshot.withError(this.error, [this.stackTrace]) : state = RefreshFutureBuilderState.error, data = null; |
||||
const RefreshFutureBuilderSnapshot.withData(this.data) : state = RefreshFutureBuilderState.done, error = null, stackTrace = null; |
||||
const RefreshFutureBuilderSnapshot.refresh(this.data, [this.error, this.stackTrace]) : state = RefreshFutureBuilderState.refreshing; |
||||
const RefreshFutureBuilderSnapshot.refreshError(this.data, this.error, this.stackTrace) : state = RefreshFutureBuilderState.refreshError; |
||||
} |
||||
|
||||
enum RefreshFutureBuilderState { |
||||
none, |
||||
initial, |
||||
waiting, |
||||
error, |
||||
done, |
||||
refreshing, |
||||
refreshError, |
||||
} |
@ -0,0 +1,209 @@
|
||||
import 'dart:convert'; |
||||
|
||||
import 'package:flutter/widgets.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_material.dart'; |
||||
import 'package:info_tren/models/train_operator_lines.dart'; |
||||
import 'package:info_tren/models/ui_design.dart'; |
||||
import 'package:info_tren/utils/default_ui_design.dart'; |
||||
import 'package:tuple/tuple.dart'; |
||||
|
||||
class SelectTrainSuggestions extends StatefulWidget { |
||||
final UiDesign? uiDesign; |
||||
final String userInput; |
||||
final void Function(int trainNumber) onTrainSelected; |
||||
|
||||
const SelectTrainSuggestions({ Key? key, required this.uiDesign, required this.userInput, required this.onTrainSelected }) : super(key: key); |
||||
|
||||
@override |
||||
SelectTrainSuggestionsState createState() { |
||||
final uiDesign = this.uiDesign ?? defaultUiDesign; |
||||
switch(uiDesign) { |
||||
case UiDesign.MATERIAL: |
||||
return SelectTrainSuggestionsStateMaterial(); |
||||
case UiDesign.CUPERTINO: |
||||
return SelectTrainSuggestionsStateCupertino(); |
||||
default: |
||||
throw UnmatchedUiDesignException(uiDesign); |
||||
} |
||||
} |
||||
} |
||||
|
||||
abstract class SelectTrainSuggestionsState extends State<SelectTrainSuggestions> { |
||||
late String userInput; |
||||
|
||||
List<TrainOperatorLines> operators = []; |
||||
|
||||
Future loadOperators(BuildContext context) async { |
||||
operators = []; |
||||
|
||||
final operatorsString = await DefaultAssetBundle.of(context).loadString("assets/lines/files.txt"); |
||||
final operatorsFilesList = operatorsString.split("\n"); |
||||
|
||||
final decoder = JsonDecoder(); |
||||
|
||||
for (final operatorFile in operatorsFilesList) { |
||||
final operatorString = await DefaultAssetBundle.of(context).loadString("assets/lines/$operatorFile"); |
||||
final operatorData = decoder.convert(operatorString); |
||||
final _operator = TrainOperatorLines.fromJson(operatorData); |
||||
operators.add(_operator); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
userInput = widget.userInput; |
||||
|
||||
loadOperators(context).then((_) { |
||||
setState(() {}); |
||||
}); |
||||
} |
||||
|
||||
@override |
||||
void didChangeDependencies() { |
||||
super.didChangeDependencies(); |
||||
if (userInput != widget.userInput) { |
||||
setState(() { |
||||
userInput = widget.userInput; |
||||
}); |
||||
} |
||||
} |
||||
|
||||
String getUseCurrentInputWidgetText(int currentInput) => 'Caută trenul cu numărul $currentInput'; |
||||
Widget getUseCurrentInputWidget(int currentInput, void Function(int) onTrainSelected); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
var sliversTuple = operators.map( |
||||
(op) => Tuple2( |
||||
getFilteredLines(op, userInput), |
||||
op.operator, |
||||
) |
||||
).where((tuple) => tuple.item1.isNotEmpty).toList(); |
||||
if (userInput.isNotEmpty) sliversTuple.sort((a, b) { |
||||
final aTrain = a.item1.first; |
||||
final bTrain = b.item1.first; |
||||
|
||||
final inputAsRegExp = RegExp(userInput); |
||||
|
||||
final matchOnA = inputAsRegExp.firstMatch(aTrain.number)!; |
||||
final matchOnB = inputAsRegExp.firstMatch(bTrain.number)!; |
||||
|
||||
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start; |
||||
|
||||
if (aTrain.number.length != bTrain.number.length) return aTrain.number.length - bTrain.number.length; |
||||
|
||||
return aTrain.number.compareTo(bTrain.number); |
||||
}); |
||||
var slivers = sliversTuple.map((tuple) => OperatorAutocompleteSliver( |
||||
uiDesign: widget.uiDesign, |
||||
operatorName: tuple.item2, |
||||
trains: tuple.item1, |
||||
onTrainSelected: widget.onTrainSelected, |
||||
)).toList(); |
||||
|
||||
return CustomScrollView( |
||||
slivers: <Widget>[ |
||||
...slivers, |
||||
SliverToBoxAdapter( |
||||
child: int.tryParse(userInput) != null ? getUseCurrentInputWidget(int.parse(userInput), widget.onTrainSelected) : Container(), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Container( |
||||
height: MediaQuery.of(context).viewPadding.bottom, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
|
||||
List<TrainOperatorTrainDescription> getFilteredLines(TrainOperatorLines _operator, String currentInput) { |
||||
if (currentInput.isNotEmpty) { |
||||
final filteredLines = _operator.trains |
||||
.where((elem) => elem.number.contains(currentInput)) |
||||
.toList(); |
||||
|
||||
filteredLines.sort((a, b) { |
||||
final inputAsRegExp = RegExp(currentInput); |
||||
|
||||
final matchOnA = inputAsRegExp.firstMatch(a.number)!; |
||||
final matchOnB = inputAsRegExp.firstMatch(b.number)!; |
||||
|
||||
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start; |
||||
|
||||
if (a.number.length != b.number.length) return a.number.length - b.number.length; |
||||
|
||||
return a.number.compareTo(b.number); |
||||
}); |
||||
|
||||
return filteredLines; |
||||
} |
||||
else { |
||||
return _operator.trains; |
||||
} |
||||
} |
||||
} |
||||
|
||||
class OperatorAutocompleteSliver extends StatelessWidget { |
||||
final UiDesign? uiDesign; |
||||
final String operatorName; |
||||
final List<TrainOperatorTrainDescription> trains; |
||||
final void Function(int) onTrainSelected; |
||||
|
||||
const OperatorAutocompleteSliver({ Key? key, required this.uiDesign, required this.operatorName, required this.trains, required this.onTrainSelected }) : super(key: key); |
||||
|
||||
Widget mapTrainToItem(TrainOperatorTrainDescription train) { |
||||
final uiDesign = this.uiDesign ?? defaultUiDesign; |
||||
switch (uiDesign) { |
||||
case UiDesign.MATERIAL: |
||||
return OperatorAutocompleteTileMaterial( |
||||
onTrainSelected: onTrainSelected, |
||||
operatorName: operatorName, |
||||
train: train, |
||||
); |
||||
case UiDesign.CUPERTINO: |
||||
return OperatorAutocompleteTileCupertino( |
||||
onTrainSelected: onTrainSelected, |
||||
operatorName: operatorName, |
||||
train: train, |
||||
); |
||||
default: |
||||
throw UnmatchedUiDesignException(uiDesign); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (trains.isEmpty) { |
||||
return SliverToBoxAdapter(child: Container(),); |
||||
} |
||||
|
||||
return SliverPrototypeExtentList( |
||||
prototypeItem: Column( |
||||
children: <Widget>[ |
||||
mapTrainToItem(TrainOperatorTrainDescription()), |
||||
], |
||||
), |
||||
delegate: SliverChildBuilderDelegate( |
||||
(context, index) { |
||||
return Column( |
||||
children: <Widget>[ |
||||
mapTrainToItem(trains[index]), |
||||
], |
||||
); |
||||
}, |
||||
childCount: trains.length, |
||||
addSemanticIndexes: true, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
abstract class OperatorAutocompleteTile extends StatelessWidget { |
||||
final String operatorName; |
||||
final TrainOperatorTrainDescription train; |
||||
final void Function(int) onTrainSelected; |
||||
|
||||
const OperatorAutocompleteTile({ Key? key, required this.onTrainSelected, required this.operatorName, required this.train }) : super(key: key); |
||||
} |
@ -0,0 +1,74 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; |
||||
import 'package:info_tren/models/train_operator_lines.dart'; |
||||
|
||||
class SelectTrainSuggestionsStateCupertino extends SelectTrainSuggestionsState { |
||||
@override |
||||
Widget getUseCurrentInputWidget(int currentInput, void Function(int p1) onTrainSelected) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
GestureDetector( |
||||
onTap: () { |
||||
onTrainSelected(currentInput); |
||||
}, |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(8), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text(getUseCurrentInputWidgetText(currentInput)), |
||||
], |
||||
) |
||||
), |
||||
), |
||||
Divider(), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class OperatorAutocompleteTileCupertino extends OperatorAutocompleteTile { |
||||
OperatorAutocompleteTileCupertino({ |
||||
Key? key, |
||||
required String operatorName, |
||||
required void Function(int) onTrainSelected, |
||||
required TrainOperatorTrainDescription train |
||||
}): super( |
||||
onTrainSelected: onTrainSelected, |
||||
operatorName: operatorName, |
||||
train: train, |
||||
key: key, |
||||
); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return GestureDetector( |
||||
onTap: () { |
||||
onTrainSelected(train.internalNumber); |
||||
}, |
||||
child: Padding( |
||||
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2), |
||||
child: SizedBox( |
||||
width: double.infinity, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.stretch, |
||||
children: <Widget>[ |
||||
Text( |
||||
operatorName, |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200), |
||||
textAlign: TextAlign.left, |
||||
), |
||||
Text( |
||||
"${train.rang} ${train.number}", |
||||
textAlign: TextAlign.left, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; |
||||
import 'package:info_tren/models/train_operator_lines.dart'; |
||||
|
||||
class SelectTrainSuggestionsStateMaterial extends SelectTrainSuggestionsState { |
||||
@override |
||||
Widget getUseCurrentInputWidget(int currentInput, void Function(int) onTrainSelected) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
ListTile( |
||||
title: Text(getUseCurrentInputWidgetText(currentInput)), |
||||
onTap: () { |
||||
onTrainSelected(currentInput); |
||||
}, |
||||
), |
||||
Divider(), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class OperatorAutocompleteTileMaterial extends OperatorAutocompleteTile { |
||||
OperatorAutocompleteTileMaterial({ |
||||
Key? key, |
||||
required String operatorName, |
||||
required void Function(int) onTrainSelected, |
||||
required TrainOperatorTrainDescription train |
||||
}): super( |
||||
onTrainSelected: onTrainSelected, |
||||
operatorName: operatorName, |
||||
train: train, |
||||
key: key, |
||||
); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return ListTile( |
||||
dense: true, |
||||
title: Text("${train.rang} ${train.number}"), |
||||
subtitle: Text(operatorName), |
||||
onTap: () { |
||||
onTrainSelected(train.internalNumber); |
||||
}, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart'; |
||||
|
||||
class SlimAppBar extends StatelessWidget { |
||||
final String title; |
||||
final double size; |
||||
// final Function onBackTap; |
||||
|
||||
SlimAppBar({ |
||||
required this.title, |
||||
this.size = 24, |
||||
// this.onBackTap, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SizedBox( |
||||
width: double.infinity, |
||||
height: size, |
||||
child: Container( |
||||
color: |
||||
Theme.of(context).appBarTheme.color ?? |
||||
Theme.of(context).primaryColor, |
||||
child: InkWell( |
||||
onTap: (ModalRoute.of(context)?.canPop ?? false) |
||||
? () => Navigator.of(context).pop() |
||||
: null, |
||||
child: Row( |
||||
mainAxisSize: MainAxisSize.max, |
||||
crossAxisAlignment: CrossAxisAlignment.stretch, |
||||
children: <Widget>[ |
||||
Container( |
||||
height: size, |
||||
width: size, |
||||
child: (ModalRoute.of(context)?.canPop ?? false) |
||||
? BackButtonIcon() |
||||
: null, |
||||
), |
||||
Expanded( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Text( |
||||
title, |
||||
textAlign: TextAlign.center, |
||||
style: |
||||
Theme.of(context).appBarTheme.textTheme?.caption?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.bodyText2?.color) ?? |
||||
Theme.of(context).textTheme.caption?.copyWith(color: Theme.of(context).textTheme.bodyText2?.color), |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Container( |
||||
height: size, |
||||
width: size, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -1,23 +0,0 @@
|
||||
|
||||
import 'package:flutter/widgets.dart'; |
||||
import 'package:webview_flutter/webview_flutter.dart'; |
||||
|
||||
class HiddenWebView extends StatelessWidget { |
||||
final WebView webView; |
||||
final Widget child; |
||||
|
||||
HiddenWebView({@required this.child, this.webView}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Stack( |
||||
children: <Widget>[ |
||||
Offstage( |
||||
offstage: true, |
||||
child: webView, |
||||
), |
||||
Positioned.fill(child: child) |
||||
], |
||||
); |
||||
} |
||||
} |
@ -1,98 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||
|
||||
part of 'train_data.dart'; |
||||
|
||||
// ************************************************************************** |
||||
// JsonSerializableGenerator |
||||
// ************************************************************************** |
||||
|
||||
TrainData _$TrainDataFromJson(Map<String, dynamic> json) { |
||||
return TrainData( |
||||
rang: json['rang'] as String, |
||||
trainNumber: json['tren'] as String, |
||||
operator: json['operator'] as String, |
||||
lastInfo: json['ultima_informatie'] == null |
||||
? null |
||||
: LastInfo.fromJson( |
||||
json['ultima_informatie'] as Map<String, dynamic>), |
||||
state: json['stare'] as String, |
||||
route: json['relatia'] as String, |
||||
tripLength: json['durata_calatoriei'] as String, |
||||
stations: (json['stations'] as List) |
||||
?.map((e) => e == null |
||||
? null |
||||
: StationEntry.fromJson(e as Map<String, dynamic>)) |
||||
?.toList(), |
||||
nextStop: json['urmatoarea_oprire'] == null |
||||
? null |
||||
: StopInfo.fromJson( |
||||
json['urmatoarea_oprire'] as Map<String, dynamic>), |
||||
distance: json['distanta'] as String, |
||||
destination: json['destinatie'] == null |
||||
? null |
||||
: StopInfo.fromJson(json['destinatie'] as Map<String, dynamic>)); |
||||
} |
||||
|
||||
Map<String, dynamic> _$TrainDataToJson(TrainData instance) => <String, dynamic>{ |
||||
'rang': instance.rang, |
||||
'tren': instance.trainNumber, |
||||
'operator': instance.operator, |
||||
'relatia': instance.route, |
||||
'stare': instance.state, |
||||
'ultima_informatie': instance.lastInfo, |
||||
'destinatie': instance.destination, |
||||
'urmatoarea_oprire': instance.nextStop, |
||||
'durata_calatoriei': instance.tripLength, |
||||
'distanta': instance.distance, |
||||
'stations': instance.stations |
||||
}; |
||||
|
||||
LastInfo _$LastInfoFromJson(Map<String, dynamic> json) { |
||||
return LastInfo( |
||||
dateAndTime: json['data_si_ora'] as String, |
||||
delay: json['intarziere'] as int, |
||||
event: json['eveniment'] as String, |
||||
station: json['statia'] as String); |
||||
} |
||||
|
||||
Map<String, dynamic> _$LastInfoToJson(LastInfo instance) => <String, dynamic>{ |
||||
'statia': instance.station, |
||||
'eveniment': instance.event, |
||||
'data_si_ora': instance.dateAndTime, |
||||
'intarziere': instance.delay |
||||
}; |
||||
|
||||
StopInfo _$StopInfoFromJson(Map<String, dynamic> json) { |
||||
return StopInfo( |
||||
station: json['statia'] as String, |
||||
dateAndTime: json['data_si_ora'] as String); |
||||
} |
||||
|
||||
Map<String, dynamic> _$StopInfoToJson(StopInfo instance) => <String, dynamic>{ |
||||
'statia': instance.station, |
||||
'data_si_ora': instance.dateAndTime |
||||
}; |
||||
|
||||
StationEntry _$StationEntryFromJson(Map<String, dynamic> json) { |
||||
return StationEntry( |
||||
name: json['statia'] as String, |
||||
delay: json['intarziere'] as int, |
||||
realOrEstimate: json['real/estimat'] as String, |
||||
arrivalTime: json['sosire'] as String, |
||||
departureTime: json['plecare'] as String, |
||||
km: json['km'] as String, |
||||
observations: json['observatii'] as String, |
||||
waitTime: json['stationeaza_pentru'] as String); |
||||
} |
||||
|
||||
Map<String, dynamic> _$StationEntryToJson(StationEntry instance) => |
||||
<String, dynamic>{ |
||||
'km': instance.km, |
||||
'statia': instance.name, |
||||
'sosire': instance.arrivalTime, |
||||
'stationeaza_pentru': instance.waitTime, |
||||
'plecare': instance.departureTime, |
||||
'real/estimat': instance.realOrEstimate, |
||||
'intarziere': instance.delay, |
||||
'observatii': instance.observations |
||||
}; |
@ -0,0 +1,42 @@
|
||||
import 'package:json_annotation/json_annotation.dart'; |
||||
|
||||
part 'train_operator_lines.g.dart'; |
||||
|
||||
@JsonSerializable() |
||||
class TrainOperatorLines { |
||||
@JsonKey(name: "short_name") |
||||
final String shortName; |
||||
final String operator; |
||||
@JsonKey(name: "versiune") |
||||
final String version; |
||||
@JsonKey(name: "trenuri") |
||||
final List<TrainOperatorTrainDescription> trains; |
||||
|
||||
TrainOperatorLines({ |
||||
required this.operator, |
||||
this.shortName = "", |
||||
required this.version, |
||||
required this.trains, |
||||
}); |
||||
|
||||
factory TrainOperatorLines.fromJson(Map<String, dynamic> json) => _$TrainOperatorLinesFromJson(json); |
||||
Map<String, dynamic> toJson() => _$TrainOperatorLinesToJson(this); |
||||
} |
||||
|
||||
@JsonSerializable() |
||||
class TrainOperatorTrainDescription { |
||||
final String rang; |
||||
@JsonKey(name: "numar") |
||||
final String number; |
||||
@JsonKey(name: "numar_intern") |
||||
final int internalNumber; |
||||
|
||||
TrainOperatorTrainDescription({ |
||||
this.number = '', |
||||
this.rang = '', |
||||
this.internalNumber = 0, |
||||
}); |
||||
|
||||
factory TrainOperatorTrainDescription.fromJson(Map<String, dynamic> json) => _$TrainOperatorTrainDescriptionFromJson(json); |
||||
Map<String, dynamic> toJson() => _$TrainOperatorTrainDescriptionToJson(this); |
||||
} |
@ -0,0 +1,42 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||
|
||||
part of 'train_operator_lines.dart'; |
||||
|
||||
// ************************************************************************** |
||||
// JsonSerializableGenerator |
||||
// ************************************************************************** |
||||
|
||||
TrainOperatorLines _$TrainOperatorLinesFromJson(Map<String, dynamic> json) => |
||||
TrainOperatorLines( |
||||
operator: json['operator'] as String, |
||||
shortName: json['short_name'] as String? ?? "", |
||||
version: json['versiune'] as String, |
||||
trains: (json['trenuri'] as List<dynamic>) |
||||
.map((e) => |
||||
TrainOperatorTrainDescription.fromJson(e as Map<String, dynamic>)) |
||||
.toList(), |
||||
); |
||||
|
||||
Map<String, dynamic> _$TrainOperatorLinesToJson(TrainOperatorLines instance) => |
||||
<String, dynamic>{ |
||||
'short_name': instance.shortName, |
||||
'operator': instance.operator, |
||||
'versiune': instance.version, |
||||
'trenuri': instance.trains, |
||||
}; |
||||
|
||||
TrainOperatorTrainDescription _$TrainOperatorTrainDescriptionFromJson( |
||||
Map<String, dynamic> json) => |
||||
TrainOperatorTrainDescription( |
||||
number: json['numar'] as String? ?? '', |
||||
rang: json['rang'] as String? ?? '', |
||||
internalNumber: json['numar_intern'] as int? ?? 0, |
||||
); |
||||
|
||||
Map<String, dynamic> _$TrainOperatorTrainDescriptionToJson( |
||||
TrainOperatorTrainDescription instance) => |
||||
<String, dynamic>{ |
||||
'rang': instance.rang, |
||||
'numar': instance.number, |
||||
'numar_intern': instance.internalNumber, |
||||
}; |
@ -0,0 +1,15 @@
|
||||
enum UiDesign { |
||||
MATERIAL, |
||||
CUPERTINO |
||||
} |
||||
|
||||
class UnmatchedUiDesignException implements Exception { |
||||
final UiDesign uiDesign; |
||||
|
||||
UnmatchedUiDesignException(this.uiDesign); |
||||
|
||||
@override |
||||
String toString() { |
||||
return '$uiDesign was not matched'; |
||||
} |
||||
} |
@ -0,0 +1,68 @@
|
||||
import 'package:flutter/widgets.dart'; |
||||
import 'package:info_tren/models/ui_design.dart'; |
||||
import 'package:info_tren/pages/main/main_page_cupertino.dart'; |
||||
import 'package:info_tren/pages/main/main_page_material.dart'; |
||||
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; |
||||
import 'package:info_tren/utils/default_ui_design.dart'; |
||||
|
||||
class MainPage extends StatelessWidget { |
||||
final UiDesign? uiDesign; |
||||
|
||||
const MainPage({ Key? key, this.uiDesign }) : super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final uiDesign = this.uiDesign ?? defaultUiDesign; |
||||
|
||||
switch (uiDesign) { |
||||
case UiDesign.MATERIAL: |
||||
return MainPageMaterial(); |
||||
case UiDesign.CUPERTINO: |
||||
return MainPageCupertino(); |
||||
default: |
||||
throw UnmatchedUiDesignException(uiDesign); |
||||
} |
||||
} |
||||
} |
||||
|
||||
abstract class MainPageShared extends StatelessWidget { |
||||
final String pageTitle = 'Info Tren'; |
||||
|
||||
List<MainPageOption> get options => [ |
||||
MainPageOption( |
||||
name: 'Informații despre tren', |
||||
action: (BuildContext context) { |
||||
onTrainInfoPageInvoke(context); |
||||
}, |
||||
), |
||||
MainPageOption( |
||||
name: 'Tabelă plecari/sosiri', |
||||
// TODO: Implement departure/arrival |
||||
action: null, |
||||
), |
||||
MainPageOption( |
||||
name: 'Planificare rută', |
||||
// TODO: Implement route planning |
||||
action: null, |
||||
), |
||||
]; |
||||
|
||||
onTrainInfoPageInvoke(BuildContext context) { |
||||
Navigator.of(context).pushNamed(SelectTrainPage.routeName); |
||||
} |
||||
|
||||
onStationBoardPageInvoke(BuildContext context) { |
||||
|
||||
} |
||||
|
||||
onRoutePlanPageInvoke(BuildContext context) { |
||||
|
||||
} |
||||
} |
||||
|
||||
class MainPageOption { |
||||
final String name; |
||||
final void Function(BuildContext context)? action; |
||||
|
||||
MainPageOption({required this.name, this.action}); |
||||
} |
@ -0,0 +1,30 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:info_tren/pages/main/main_page.dart'; |
||||
|
||||
class MainPageCupertino extends MainPageShared { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return CupertinoPageScaffold( |
||||
navigationBar: CupertinoNavigationBar( |
||||
middle: Text(pageTitle), |
||||
), |
||||
child: SafeArea( |
||||
child: Center( |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: options.map((option) => CupertinoButton.filled( |
||||
child: Text(option.name), |
||||
onPressed: option.action == null ? null : () => option.action!(context), |
||||
)).map((w) => Padding( |
||||
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), |
||||
child: SizedBox( |
||||
width: double.infinity, |
||||
child: w, |
||||
), |
||||
)).toList(), |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:info_tren/pages/main/main_page.dart'; |
||||
|
||||
class MainPageMaterial extends MainPageShared { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: Text(pageTitle), |
||||
centerTitle: true, |
||||
), |
||||
body: SafeArea( |
||||
child: Center( |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: options.map((option) => ElevatedButton( |
||||
child: Text( |
||||
option.name, |
||||
style: Theme.of(context).textTheme.button?.copyWith(fontSize: 18), |
||||
), |
||||
onPressed: option.action != null ? () => option.action!(context) : null, |
||||
)).map((w) => Padding( |
||||
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), |
||||
child: SizedBox( |
||||
width: double.infinity, |
||||
child: w, |
||||
), |
||||
)).toList(), |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,61 @@
|
||||
import 'dart:io' show Platform; |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; |
||||
import 'package:info_tren/models/ui_design.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_material.dart'; |
||||
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; |
||||
import 'package:info_tren/utils/default_ui_design.dart'; |
||||
import 'package:tuple/tuple.dart'; |
||||
|
||||
typedef TrainSelectedCallback(int trainNumber); |
||||
|
||||
class SelectTrainPage extends StatefulWidget { |
||||
final UiDesign? uiDesign; |
||||
|
||||
SelectTrainPage({Key? key, this.uiDesign}) : super(key: key); |
||||
|
||||
static String routeName = "/trainInfo/selectTrain"; |
||||
|
||||
void onTrainSelected(BuildContext context, int selection) { |
||||
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: selection); |
||||
} |
||||
|
||||
@override |
||||
SelectTrainPageState createState() { |
||||
final uiDesign = this.uiDesign ?? defaultUiDesign; |
||||
switch(uiDesign) { |
||||
case UiDesign.MATERIAL: |
||||
return SelectTrainPageStateMaterial(); |
||||
case UiDesign.CUPERTINO: |
||||
return SelectTrainPageStateCupertino(); |
||||
default: |
||||
throw UnmatchedUiDesignException(uiDesign); |
||||
} |
||||
} |
||||
} |
||||
|
||||
abstract class SelectTrainPageState extends State<SelectTrainPage> { |
||||
final String pageTitle = 'Informații despre tren'; |
||||
final String textFieldLabel = 'Numărul trenului'; |
||||
|
||||
TextEditingController trainNoController = TextEditingController(); |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
} |
||||
|
||||
void onTextChanged() { |
||||
setState(() {}); |
||||
} |
||||
|
||||
Widget get suggestionsList => SelectTrainSuggestions( |
||||
uiDesign: widget.uiDesign, |
||||
userInput: trainNoController.text, |
||||
onTrainSelected: (trainNumber) => widget.onTrainSelected(context, trainNumber), |
||||
key: ValueKey(trainNoController.text), |
||||
); |
||||
} |
@ -0,0 +1,39 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:flutter/services.dart'; |
||||
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; |
||||
|
||||
class SelectTrainPageStateCupertino extends SelectTrainPageState { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return CupertinoPageScaffold( |
||||
navigationBar: CupertinoNavigationBar( |
||||
middle: Text(pageTitle), |
||||
), |
||||
child: SafeArea( |
||||
bottom: false, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: CupertinoTextField( |
||||
controller: trainNoController, |
||||
autofocus: true, |
||||
placeholder: textFieldLabel, |
||||
textInputAction: TextInputAction.search, |
||||
keyboardType: TextInputType.number, |
||||
onChanged: (_) => onTextChanged(), |
||||
inputFormatters: [ |
||||
FilteringTextInputFormatter.digitsOnly, |
||||
], |
||||
), |
||||
), |
||||
Expanded( |
||||
child: suggestionsList, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter/services.dart'; |
||||
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; |
||||
|
||||
class SelectTrainPageStateMaterial extends SelectTrainPageState { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: Text(pageTitle), |
||||
centerTitle: true, |
||||
), |
||||
body: SafeArea( |
||||
bottom: false, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: TextField( |
||||
controller: trainNoController, |
||||
autofocus: true, |
||||
decoration: InputDecoration( |
||||
border: OutlineInputBorder(), |
||||
labelText: textFieldLabel, |
||||
), |
||||
inputFormatters: [ |
||||
FilteringTextInputFormatter.digitsOnly, |
||||
], |
||||
textInputAction: TextInputAction.search, |
||||
keyboardType: TextInputType.number, |
||||
onChanged: (_) => onTextChanged(), |
||||
), |
||||
), |
||||
Expanded( |
||||
child: suggestionsList, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:info_tren/train_info_page/train_info_constants.dart'; |
||||
import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; |
||||
|
||||
import 'dart:io' show Platform; |
||||
|
@ -0,0 +1,69 @@
|
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:info_tren/api/train_data.dart'; |
||||
import 'package:info_tren/components/loading/loading.dart'; |
||||
import 'package:info_tren/components/refresh_future_builder.dart'; |
||||
import 'package:info_tren/models/train_data.dart'; |
||||
import 'package:info_tren/models/ui_design.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_material.dart'; |
||||
import 'package:info_tren/utils/default_ui_design.dart'; |
||||
|
||||
|
||||
class TrainInfo extends StatelessWidget { |
||||
static String routeName = "/trainInfo/display"; |
||||
|
||||
final UiDesign? uiDesign; |
||||
final int trainNumber; |
||||
|
||||
TrainInfo({Key? key, required this.trainNumber, this.uiDesign}): super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final uiDesign = this.uiDesign ?? defaultUiDesign; |
||||
|
||||
return RefreshFutureBuilder<TrainData>( |
||||
futureCreator: () => getTrain(trainNumber), |
||||
builder: (context, refresh, snapshot) { |
||||
|
||||
switch (uiDesign) { |
||||
case UiDesign.MATERIAL: |
||||
if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) { |
||||
return TrainInfoLoadingMaterial(title: trainNumber.toString(),); |
||||
} |
||||
else if (snapshot.state == RefreshFutureBuilderState.error) { |
||||
return TrainInfoErrorMaterial(title: '$trainNumber - Error', error: snapshot.error!,); |
||||
} |
||||
|
||||
return TrainInfoMaterial(trainData: snapshot.data!,); |
||||
case UiDesign.CUPERTINO: |
||||
if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) { |
||||
return TrainInfoLoadingCupertino(title: trainNumber.toString(),); |
||||
} |
||||
else if (snapshot.state == RefreshFutureBuilderState.error) { |
||||
return TrainInfoErrorCupertino(title: '$trainNumber - Error', error: snapshot.error!,); |
||||
} |
||||
|
||||
return TrainInfoCupertino(trainData: snapshot.data!,); |
||||
default: |
||||
throw UnmatchedUiDesignException(uiDesign); |
||||
} |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
abstract class TrainInfoLoading extends StatelessWidget { |
||||
final String title; |
||||
final Widget loadingWidget; |
||||
|
||||
TrainInfoLoading({required this.title, String? loadingText, UiDesign? uiDesign}) : loadingWidget = Loading(uiDesign: uiDesign, text: loadingText,); |
||||
} |
||||
|
||||
abstract class TrainInfoError extends StatelessWidget { |
||||
final String title; |
||||
final Object error; |
||||
|
||||
TrainInfoError({required this.title, required this.error}); |
||||
} |
@ -0,0 +1,741 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:info_tren/components/cupertino_divider.dart'; |
||||
import 'package:info_tren/models/train_data.dart' hide State; |
||||
import 'package:info_tren/models/ui_design.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.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'; |
||||
|
||||
class TrainInfoLoadingCupertino extends TrainInfoLoading { |
||||
TrainInfoLoadingCupertino({required String title, String? loadingText}) : super(title: title, loadingText: loadingText, uiDesign: UiDesign.CUPERTINO); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return CupertinoPageScaffold( |
||||
navigationBar: CupertinoNavigationBar( |
||||
middle: Text(title), |
||||
), |
||||
child: Center( |
||||
child: loadingWidget, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class TrainInfoErrorCupertino extends TrainInfoError { |
||||
TrainInfoErrorCupertino({required Object error, required String title,}) : super(error: error, title: title,); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return CupertinoPageScaffold( |
||||
navigationBar: CupertinoNavigationBar( |
||||
middle: Text(title), |
||||
), |
||||
child: Center( |
||||
child: Text(error.toString()), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class TrainInfoCupertino extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
TrainInfoCupertino({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return CupertinoPageScaffold( |
||||
navigationBar: CupertinoNavigationBar( |
||||
middle: Text("Informații despre ${trainData.rank} ${trainData.number}"), |
||||
), |
||||
child: SafeArea( |
||||
top: false, |
||||
bottom: false, |
||||
child: Builder( |
||||
builder: (context) { |
||||
final topPadding = MediaQuery.of(context).padding.top; |
||||
|
||||
return CustomScrollView( |
||||
slivers: <Widget>[ |
||||
SliverToBoxAdapter( |
||||
child: Padding( |
||||
padding: EdgeInsets.only( |
||||
top: topPadding, |
||||
), |
||||
child: Container(), |
||||
), |
||||
), |
||||
DisplayTrainID(trainData: trainData,), |
||||
DisplayTrainOperator(trainData: trainData,), |
||||
DisplayTrainRoute(trainData: trainData,), |
||||
DisplayTrainDeparture(trainData: trainData,), |
||||
SliverToBoxAdapter( |
||||
child: CupertinoDivider( |
||||
color: FOREGROUND_WHITE, |
||||
), |
||||
), |
||||
DisplayTrainLastInfo(trainData: trainData,), |
||||
SliverToBoxAdapter( |
||||
child: CupertinoDivider(), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: IntrinsicHeight( |
||||
child: Row( |
||||
children: <Widget>[ |
||||
// Expanded( |
||||
// child: DisplayTrainNextStop(trainData: trainData,), |
||||
// ), |
||||
Expanded( |
||||
child: DisplayTrainDestination(trainData: trainData,), |
||||
), |
||||
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,), |
||||
// ) |
||||
// ], |
||||
// ), |
||||
// ), |
||||
// ), |
||||
SliverToBoxAdapter( |
||||
child: CupertinoDivider( |
||||
color: FOREGROUND_WHITE, |
||||
), |
||||
), |
||||
DisplayTrainStations( |
||||
trainData: trainData, |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Container( |
||||
height: MediaQuery.of(context).viewPadding.bottom, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
), |
||||
), |
||||
); |
||||
|
||||
// return CupertinoPageScaffold( |
||||
// navigationBar: CupertinoNavigationBar( |
||||
// middle: Text(title ?? ""), |
||||
// ), |
||||
// child: SafeArea( |
||||
// bottom: false, |
||||
// child: FutureBuilder<OnDemandTrainData>( |
||||
// future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () { |
||||
// Navigator.of(context).pop(); |
||||
// }), |
||||
// builder: (context, snapshot) { |
||||
// if (!snapshot.hasData) { |
||||
// return Center( |
||||
// child: CupertinoActivityIndicator(), |
||||
// ); |
||||
// } |
||||
|
||||
// try { |
||||
// Future.wait([ |
||||
// snapshot.data.rang, |
||||
// snapshot.data.trainNumber |
||||
// ]).then((values) { |
||||
// setState(() { |
||||
// title = "Informații despre ${values[0]} ${values[1]}"; |
||||
// }); |
||||
// }); |
||||
|
||||
// return CustomScrollView( |
||||
// slivers: <Widget>[ |
||||
// DisplayTrainID(data: snapshot.data,), |
||||
// DisplayTrainOperator(data: snapshot.data,), |
||||
// DisplayTrainRoute(data: snapshot.data,), |
||||
// DisplayTrainDeparture(data: snapshot.data,), |
||||
// SliverToBoxAdapter( |
||||
// child: CupertinoDivider( |
||||
// color: FOREGROUND_WHITE, |
||||
// ), |
||||
// ), |
||||
// DisplayTrainLastInfo(data: snapshot.data,), |
||||
// SliverToBoxAdapter( |
||||
// child: CupertinoDivider(), |
||||
// ), |
||||
// SliverToBoxAdapter( |
||||
// child: IntrinsicHeight( |
||||
// child: Row( |
||||
// children: <Widget>[ |
||||
// Expanded( |
||||
// child: DisplayTrainNextStop(data: snapshot.data,), |
||||
// ), |
||||
// SizedBox( |
||||
// height: double.infinity, |
||||
// child: CupertinoVerticalDivider(), |
||||
// ), |
||||
// Expanded( |
||||
// child: DisplayTrainDestination(data: snapshot.data,), |
||||
// ) |
||||
// ], |
||||
// ), |
||||
// ), |
||||
// ), |
||||
// SliverToBoxAdapter( |
||||
// child: CupertinoDivider(), |
||||
// ), |
||||
// SliverToBoxAdapter( |
||||
// child: IntrinsicHeight( |
||||
// child: Row( |
||||
// children: <Widget>[ |
||||
// Expanded( |
||||
// child: DisplayTrainRouteDuration(data: snapshot.data,), |
||||
// ), |
||||
// SizedBox( |
||||
// height: double.infinity, |
||||
// child: CupertinoVerticalDivider(), |
||||
// ), |
||||
// Expanded( |
||||
// child: DisplayTrainRouteDistance(data: snapshot.data,), |
||||
// ) |
||||
// ], |
||||
// ), |
||||
// ), |
||||
// ), |
||||
// SliverToBoxAdapter( |
||||
// child: CupertinoDivider( |
||||
// color: FOREGROUND_WHITE, |
||||
// ), |
||||
// ), |
||||
// DisplayTrainStations( |
||||
// data: snapshot.data, |
||||
// pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture, |
||||
// ), |
||||
// ], |
||||
// ); |
||||
// } |
||||
// on OnDemandInvalidatedException { |
||||
// Navigator.of(context).pop(); |
||||
// print("Got OnDemandInvalidatedException!"); |
||||
// return Container(); |
||||
// } |
||||
// }, |
||||
// ), |
||||
// ), |
||||
// ); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainID extends StatelessWidget { |
||||
final TrainData trainData; |
||||
DisplayTrainID({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SliverToBoxAdapter( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Text( |
||||
"${trainData.rank} ${trainData.number}", |
||||
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle, |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRoute extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainRoute({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SliverToBoxAdapter( |
||||
child: Row( |
||||
children: <Widget>[ |
||||
Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.route.from, |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Expanded(child: Container(),), |
||||
Center(child: Text("-")), |
||||
Expanded(child: Container(),), |
||||
Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.route.to, |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
textAlign: TextAlign.right, |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainOperator extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainOperator({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SliverToBoxAdapter( |
||||
child: Center( |
||||
child: Text( |
||||
trainData.operator, |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 14, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainDeparture extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainDeparture({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SliverToBoxAdapter( |
||||
child: 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: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontStyle: FontStyle.italic, |
||||
fontWeight: FontWeight.w200, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainLastInfo extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainLastInfo({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (trainData.status == null) { |
||||
return SliverToBoxAdapter(child: Container(),); |
||||
} |
||||
|
||||
return SliverToBoxAdapter( |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Text( |
||||
"Ultima informație", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 20, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Row( |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.status!.station, |
||||
style: CupertinoTheme.of(context).textTheme.textStyle, |
||||
textAlign: TextAlign.left, |
||||
), |
||||
), |
||||
Expanded(child: Container(),), |
||||
Padding( |
||||
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, |
||||
// builder: (context, dt) { |
||||
// return Text( |
||||
// "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) { |
||||
final data = trainData.status!.delay; |
||||
|
||||
if (data == 0) { |
||||
return Container(); |
||||
} |
||||
|
||||
if (data > 0) { |
||||
return Text( |
||||
"$data minute întârziere", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 14, |
||||
color: CupertinoColors.destructiveRed, |
||||
), |
||||
); |
||||
} |
||||
else { |
||||
return Text( |
||||
"${-data} minute mai devreme", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 12, |
||||
color: CupertinoColors.activeGreen, |
||||
), |
||||
); |
||||
} |
||||
}, |
||||
) |
||||
], |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
// class DisplayTrainNextStop extends StatelessWidget { |
||||
// final TrainData trainData; |
||||
// |
||||
// DisplayTrainNextStop({required this.trainData}); |
||||
// |
||||
// @override |
||||
// Widget build(BuildContext context) { |
||||
// return FutureBuilder( |
||||
// future: trainData.nextStop.stationName, |
||||
// builder: (context, snapshot) { |
||||
// if (!snapshot.hasData) return Container(); |
||||
// |
||||
// return Column( |
||||
// mainAxisSize: MainAxisSize.min, |
||||
// children: <Widget>[ |
||||
// Padding( |
||||
// padding: const EdgeInsets.all(4), |
||||
// child: Text( |
||||
// "Următoarea oprire", |
||||
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
// fontSize: 20, |
||||
// fontWeight: FontWeight.bold, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// ), |
||||
// CupertinoDivider( |
||||
// color: Color.fromRGBO(15, 15, 15, 1), |
||||
// ), |
||||
// FutureDisplay( |
||||
// future: trainData.nextStop.stationName, |
||||
// builder: (context, station) { |
||||
// return Padding( |
||||
// padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), |
||||
// child: Text( |
||||
// station, |
||||
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
// fontSize: 18, |
||||
// fontWeight: FontWeight.w500, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// ); |
||||
// }, |
||||
// ), |
||||
// FutureDisplay<DateTime>( |
||||
// future: trainData.nextStop.arrival, |
||||
// builder: (context, arrival) { |
||||
// 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: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
// fontSize: 14, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// Text( |
||||
// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", |
||||
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
// fontSize: 14, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// ], |
||||
// ); |
||||
// }, |
||||
// ) |
||||
// ], |
||||
// ); |
||||
// } |
||||
// ); |
||||
// } |
||||
// } |
||||
|
||||
class DisplayTrainDestination extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainDestination({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
"Destinația", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 20, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
CupertinoDivider( |
||||
color: Color.fromRGBO(15, 15, 15, 1), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), |
||||
child: Text( |
||||
trainData.stations.last.name, |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 18, |
||||
fontWeight: FontWeight.w500, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
final arrival = trainData.stations.last.arrival!.scheduleTime; |
||||
final delay = trainData.stations.last.arrival!.status?.delay ?? 0; |
||||
final parts = arrival.split(':'); |
||||
final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1])); |
||||
final arrivalWithDelay = arrivalDT.add(Duration(minutes: delay)); |
||||
final arrivalWithDelayString = '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}'; |
||||
// 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: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
// fontSize: 14, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
Text.rich( |
||||
TextSpan( |
||||
text: 'la', |
||||
children: [ |
||||
TextSpan(text: ' '), |
||||
TextSpan( |
||||
text: '$arrival', |
||||
style: delay == 0 ? null : TextStyle( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
if (delay != 0) ...[ |
||||
TextSpan(text: ' '), |
||||
TextSpan( |
||||
text: '$arrivalWithDelayString', |
||||
style: TextStyle( |
||||
color: delay > 0 ? CupertinoColors.destructiveRed : CupertinoColors.activeGreen, |
||||
), |
||||
), |
||||
] |
||||
], |
||||
), |
||||
// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 14, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
) |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRouteDistance extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainRouteDistance({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"Distanța rutei", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 18, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Text( |
||||
"${trainData.stations.last.km} km", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
// class DisplayTrainRouteDuration extends StatelessWidget { |
||||
// final TrainData trainData; |
||||
// |
||||
// DisplayTrainRouteDuration({required this.trainData}); |
||||
// |
||||
// @override |
||||
// Widget build(BuildContext context) { |
||||
// return Column( |
||||
// mainAxisSize: MainAxisSize.min, |
||||
// children: <Widget>[ |
||||
// Text( |
||||
// "Durata rutei", |
||||
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
// fontSize: 18, |
||||
// fontWeight: FontWeight.bold, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// FutureDisplay<Duration>( |
||||
// future: trainData.routeDuration, |
||||
// builder: (context, duration) { |
||||
// var durationString = StringBuffer(); |
||||
// |
||||
// bool firstWritten = false; |
||||
// |
||||
// if (duration.inDays > 0) { |
||||
// firstWritten = true; |
||||
// if (duration.inDays == 1) durationString.write("1 zi"); |
||||
// else durationString.write("${duration.inDays} zile"); |
||||
// duration -= Duration(days: duration.inDays); |
||||
// } |
||||
// |
||||
// if (duration.inHours > 0) { |
||||
// if (firstWritten) { |
||||
// durationString.write(", "); |
||||
// } |
||||
// firstWritten = true; |
||||
// if (duration.inHours == 1) durationString.write("1 oră"); |
||||
// else durationString.write("${duration.inHours} ore"); |
||||
// duration -= Duration(hours: duration.inHours); |
||||
// } |
||||
// |
||||
// if (duration.inMinutes > 0) { |
||||
// if (firstWritten) { |
||||
// durationString.write(", "); |
||||
// } |
||||
// firstWritten = true; |
||||
// if (duration.inMinutes == 1) durationString.write("1 minut"); |
||||
// else durationString.write("${duration.inMinutes} minute"); |
||||
// duration -= Duration(minutes: duration.inMinutes); |
||||
// } |
||||
// |
||||
// return Text( |
||||
// durationString.toString(), |
||||
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
// fontSize: 16, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ); |
||||
// }, |
||||
// ), |
||||
// ], |
||||
// ); |
||||
// } |
||||
// } |
||||
|
||||
class DisplayTrainStations extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainStations({required this.trainData,}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SliverList( |
||||
delegate: SliverChildBuilderDelegate( |
||||
(context, index) { |
||||
if (index.isOdd) { |
||||
return CupertinoDivider(); |
||||
} |
||||
else { |
||||
final itemIndex = index ~/ 2; |
||||
return IndexedSemantics( |
||||
child: DisplayTrainStation( |
||||
station: trainData.stations[itemIndex], |
||||
), |
||||
index: itemIndex, |
||||
); |
||||
} |
||||
}, |
||||
childCount: trainData.stations.length * 2 - 1, |
||||
addSemanticIndexes: false, |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,466 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:info_tren/models/train_data.dart'; |
||||
import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; |
||||
|
||||
class DisplayTrainStation extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
DisplayTrainStation({required this.station}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Row( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
Builder( |
||||
builder: (context) { |
||||
final delay = station.departure?.status?.delay ?? station.arrival?.status?.delay; |
||||
final real = station.departure?.status?.real ?? station.arrival?.status?.real; |
||||
|
||||
final isDelayed = delay != null && delay > 0 && real == true; |
||||
final isOnTime = delay != null && delay <= 0 && real == true; |
||||
final isNotScheduled = false; |
||||
|
||||
return KmBadge( |
||||
station: station, |
||||
isNotScheduled: isNotScheduled, |
||||
isDelayed: isDelayed, |
||||
isOnTime: isOnTime, |
||||
); |
||||
} |
||||
), |
||||
Expanded( |
||||
child: Title( |
||||
station: station, |
||||
), |
||||
) |
||||
], |
||||
), |
||||
Time( |
||||
station: station, |
||||
), |
||||
Delay( |
||||
station: station, |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class KmBadge extends StatelessWidget { |
||||
final Station station; |
||||
final bool isNotScheduled; |
||||
final bool isOnTime; |
||||
final bool isDelayed; |
||||
|
||||
KmBadge({ |
||||
required this.station, |
||||
this.isNotScheduled = false, |
||||
this.isOnTime = false, |
||||
this.isDelayed = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
Color foregroundColor = FOREGROUND_WHITE; |
||||
Color? backgroundColor; |
||||
|
||||
if (isNotScheduled) { |
||||
foregroundColor = Color.fromRGBO(225, 175, 30, 1); |
||||
backgroundColor = Color.fromRGBO(80, 40, 10, 1); |
||||
} |
||||
else if (isOnTime) { |
||||
foregroundColor = Color.fromRGBO(130, 175, 65, 1); |
||||
backgroundColor = Color.fromRGBO(40, 80, 10, 1); |
||||
} |
||||
else if (isDelayed) { |
||||
foregroundColor = Color.fromRGBO(225, 75, 30, 1); |
||||
backgroundColor = Color.fromRGBO(80, 20, 10, 1); |
||||
} |
||||
|
||||
return Padding( |
||||
padding: const EdgeInsets.all(8), |
||||
child: Container( |
||||
decoration: BoxDecoration( |
||||
borderRadius: BorderRadius.circular(10), |
||||
border: Border.all( |
||||
width: 2, |
||||
color: foregroundColor, |
||||
), |
||||
color: backgroundColor, |
||||
// color: CupertinoColors.activeOrange, |
||||
), |
||||
width: 48, |
||||
height: 48, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: Center( |
||||
child: Text( |
||||
station.km.toString(), |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 20, |
||||
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, |
||||
color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
), |
||||
Text( |
||||
"km", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 10, |
||||
color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Title extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
Title({ |
||||
required this.station |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Text( |
||||
station.name, |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.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; |
||||
|
||||
Time({ |
||||
required this.station, |
||||
}); |
||||
|
||||
@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: CupertinoTheme.of(context).textTheme.textStyle.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: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class ArrivalTime extends StatelessWidget { |
||||
final Station station; |
||||
final bool finalStation; |
||||
|
||||
ArrivalTime({ |
||||
required this.station, |
||||
this.finalStation = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (finalStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Text( |
||||
"→", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
Container(width: 2,), |
||||
Text("sosire la "), |
||||
ArrivalTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final delay = station.arrival!.status?.delay ?? 0; |
||||
final time = station.arrival!.scheduleTime; |
||||
|
||||
if (delay == 0) { |
||||
return Text("$time"); |
||||
} |
||||
else if (delay > 0) { |
||||
final splits = time.split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final 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: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.destructiveRed, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final splits = time.split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final newDate = oldDate.subtract(Duration(minutes: delay)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.activeGreen, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
class StopTime extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
StopTime({ |
||||
required this.station, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final stopsFor = station.stoppingTime!; |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"staționează pentru", |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
int stopsForInt = stopsFor; |
||||
if (stopsForInt == 1) { |
||||
return Text( |
||||
"1 minut", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else if (stopsForInt < 20) { |
||||
return Text( |
||||
"$stopsFor minute", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else { |
||||
return Text( |
||||
"$stopsFor de minute", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
}, |
||||
) |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DepartureTime extends StatelessWidget { |
||||
final Station station; |
||||
final bool firstStation; |
||||
|
||||
DepartureTime({ |
||||
required this.station, |
||||
this.firstStation = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (firstStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Expanded(child: Container(),), |
||||
Text("plecare la "), |
||||
DepartureTime(station: station,), |
||||
Container(width: 2,), |
||||
Text( |
||||
"→", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final delay = station.departure!.status?.delay ?? 0; |
||||
final time = station.departure!.scheduleTime; |
||||
|
||||
if (delay == 0) { |
||||
return Text("$time"); |
||||
} |
||||
else if (delay > 0) { |
||||
final splits = time.split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final 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: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.destructiveRed, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final splits = time.split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final newDate = oldDate.subtract(Duration(minutes: delay)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.activeGreen, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
class Delay extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
Delay({ |
||||
required this.station, |
||||
}); |
||||
|
||||
@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 minute întârziere", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.destructiveRed, |
||||
fontSize: 14, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
else if (delay < 0) { |
||||
return Text( |
||||
"${-delay} minute mai devreme", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.activeGreen, |
||||
fontSize: 14, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
|
||||
return Container(); |
||||
} |
||||
} |
@ -0,0 +1,659 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:info_tren/components/slim_app_bar.dart'; |
||||
import 'package:info_tren/models/train_data.dart' hide State; |
||||
import 'package:info_tren/models/ui_design.dart'; |
||||
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; |
||||
import 'package:info_tren/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart'; |
||||
import 'package:info_tren/utils/state_to_string.dart'; |
||||
|
||||
class TrainInfoLoadingMaterial extends TrainInfoLoading { |
||||
TrainInfoLoadingMaterial({required String title, String? loadingText}) : super(title: title, loadingText: loadingText, uiDesign: UiDesign.MATERIAL); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: Text(title), |
||||
), |
||||
body: Center( |
||||
child: loadingWidget, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class TrainInfoErrorMaterial extends TrainInfoError { |
||||
TrainInfoErrorMaterial({required Object error, required String title,}) : super(error: error, title: title,); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: Text(title), |
||||
), |
||||
body: Center( |
||||
child: Text(error.toString()), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
bool isSmallScreen(BuildContext context) => MediaQuery.of(context).size.height <= 425; |
||||
|
||||
class TrainInfoMaterial extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
TrainInfoMaterial({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Builder( |
||||
builder: (context) { |
||||
return Scaffold( |
||||
appBar: isSmallScreen(context) ? null : AppBar( |
||||
centerTitle: true, |
||||
title: Text("Informații despre ${trainData.rank} ${trainData.number}"), |
||||
), |
||||
body: Column( |
||||
children: <Widget>[ |
||||
if (isSmallScreen(context)) |
||||
SlimAppBar( |
||||
title: 'INFO TREN - ${trainData.rank} ${trainData.number}' |
||||
), |
||||
Expanded( |
||||
child: SafeArea( |
||||
bottom: false, |
||||
child: 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: 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, |
||||
), |
||||
), |
||||
DisplayTrainStations( |
||||
trainData: trainData, |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Container( |
||||
height: MediaQuery.of(context).viewPadding.bottom, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainID extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainID({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Text( |
||||
"${trainData.rank} ${trainData.number}", |
||||
style: (isSmallScreen(context) |
||||
? Theme.of(context).textTheme.headline4 |
||||
: Theme.of(context).textTheme.headline3)?.copyWith( |
||||
color: Theme.of(context).textTheme.bodyText2?.color, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainOperator extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainOperator({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Text( |
||||
trainData.operator, |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontStyle: FontStyle.italic, |
||||
fontSize: isSmallScreen(context) ? 12 : 14, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRoute extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainRoute({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Row( |
||||
children: <Widget>[ |
||||
Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.route.from, |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Expanded(child: Container(),), |
||||
Center(child: Text("-")), |
||||
Expanded(child: Container(),), |
||||
Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.route.to, |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
textAlign: TextAlign.right, |
||||
), |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainDeparture extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainDeparture({required this.trainData}); |
||||
|
||||
@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: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontStyle: FontStyle.italic, |
||||
fontWeight: FontWeight.w200, |
||||
fontSize: isSmallScreen(context) ? 14 : 16, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainLastInfo extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainLastInfo({required this.trainData}); |
||||
|
||||
@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: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 20 : 22, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Row( |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
trainData.status!.station, |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 16 : 18, |
||||
), |
||||
textAlign: TextAlign.left, |
||||
), |
||||
), |
||||
Expanded(child: Container(),), |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
stateToString(trainData.status!.state), |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 16 : 18, |
||||
), |
||||
textAlign: TextAlign.right, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Row( |
||||
children: <Widget>[ |
||||
// FutureDisplay<DateTime>( |
||||
// future: trainData.lastInfo.dateAndTime, |
||||
// builder: (context, dt) { |
||||
// return Text( |
||||
// "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, |
||||
// ); |
||||
// }, |
||||
// ), |
||||
Expanded(child: Container(),), |
||||
Builder( |
||||
builder: (context) { |
||||
final data = trainData.status!.delay; |
||||
if (data == 0) { |
||||
return Container(); |
||||
} |
||||
|
||||
if (data > 0) { |
||||
return Text( |
||||
"$data minute întârziere", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 14 : 16, |
||||
color: Colors.red.shade300, |
||||
), |
||||
); |
||||
} |
||||
else { |
||||
return Text( |
||||
"${-data} minute mai devreme", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 14 : 16, |
||||
color: Colors.green.shade300, |
||||
), |
||||
); |
||||
} |
||||
}, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
// class DisplayTrainNextStop extends StatelessWidget { |
||||
// final OnDemandTrainData trainData; |
||||
// |
||||
// DisplayTrainNextStop({@required this.trainData}); |
||||
// |
||||
// @override |
||||
// Widget build(BuildContext context) { |
||||
// return FutureBuilder( |
||||
// future: trainData.nextStop.stationName, |
||||
// builder: (context, snapshot) { |
||||
// if (!snapshot.hasData) return Container(height: 0,); |
||||
// |
||||
// 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( |
||||
// "Următoarea oprire", |
||||
// style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
// fontSize: isSmallScreen(context) ? 18 : 20, |
||||
// fontWeight: FontWeight.bold, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// ), |
||||
// FutureDisplay( |
||||
// future: trainData.nextStop.stationName, |
||||
// builder: (context, station) { |
||||
// return Padding( |
||||
// padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), |
||||
// child: Text( |
||||
// station, |
||||
// style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
// fontSize: isSmallScreen(context) ? 16 : 18, |
||||
// fontWeight: FontWeight.w500, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// ); |
||||
// }, |
||||
// ), |
||||
// FutureDisplay<DateTime>( |
||||
// future: trainData.nextStop.arrival, |
||||
// builder: (context, arrival) { |
||||
// 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( |
||||
// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", |
||||
// style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
// fontSize: isSmallScreen(context) ? 12 : 14, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// ], |
||||
// ); |
||||
// }, |
||||
// ) |
||||
// ], |
||||
// ), |
||||
// ), |
||||
// ), |
||||
// ); |
||||
// } |
||||
// ); |
||||
// } |
||||
// } |
||||
|
||||
class DisplayTrainDestination extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainDestination({required this.trainData}); |
||||
|
||||
@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: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 20 : 22, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), |
||||
child: Text( |
||||
destination.name, |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 20, |
||||
fontWeight: FontWeight.w500, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
final arrival = destination.arrival!.scheduleTime; |
||||
final delay = trainData.stations.last.arrival!.status?.delay ?? 0; |
||||
final parts = arrival.split(':'); |
||||
final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1])); |
||||
final arrivalWithDelay = arrivalDT.add(Duration(minutes: delay)); |
||||
final 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( |
||||
// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", |
||||
TextSpan( |
||||
text: 'la', |
||||
children: [ |
||||
TextSpan(text: ' '), |
||||
TextSpan( |
||||
text: '$arrival', |
||||
style: delay == 0 ? null : TextStyle( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
if (delay != 0) ...[ |
||||
TextSpan(text: ' '), |
||||
TextSpan( |
||||
text: '$arrivalWithDelayString', |
||||
style: TextStyle( |
||||
color: delay > 0 ? Colors.red.shade300 : Colors.green.shade300, |
||||
), |
||||
), |
||||
] |
||||
], |
||||
), |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 14 : 16, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
) |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRouteDistance extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainRouteDistance({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"Distanța rutei", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 20 : 22, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Text( |
||||
"${trainData.stations.last.km} km", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 20, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
// class DisplayTrainRouteDuration extends StatelessWidget { |
||||
// final TrainData trainData; |
||||
// |
||||
// DisplayTrainRouteDuration({required this.trainData}); |
||||
// |
||||
// @override |
||||
// Widget build(BuildContext context) { |
||||
// return Card( |
||||
// child: Center( |
||||
// child: Padding( |
||||
// padding: const EdgeInsets.all(2), |
||||
// child: Column( |
||||
// mainAxisSize: MainAxisSize.min, |
||||
// children: <Widget>[ |
||||
// Text( |
||||
// "Durata rutei", |
||||
// style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
// fontSize: isSmallScreen(context) ? 16 : 18, |
||||
// fontWeight: FontWeight.bold, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ), |
||||
// FutureDisplay<Duration>( |
||||
// future: trainData.routeDuration, |
||||
// builder: (context, duration) { |
||||
// var durationString = StringBuffer(); |
||||
// |
||||
// bool firstWritten = false; |
||||
// |
||||
// if (duration.inDays > 0) { |
||||
// firstWritten = true; |
||||
// if (duration.inDays == 1) durationString.write("1 zi"); |
||||
// else durationString.write("${duration.inDays} zile"); |
||||
// duration -= Duration(days: duration.inDays); |
||||
// } |
||||
// |
||||
// if (duration.inHours > 0) { |
||||
// if (firstWritten) { |
||||
// durationString.write(", "); |
||||
// } |
||||
// firstWritten = true; |
||||
// if (duration.inHours == 1) durationString.write("1 oră"); |
||||
// else durationString.write("${duration.inHours} ore"); |
||||
// duration -= Duration(hours: duration.inHours); |
||||
// } |
||||
// |
||||
// if (duration.inMinutes > 0) { |
||||
// if (firstWritten) { |
||||
// durationString.write(", "); |
||||
// } |
||||
// firstWritten = true; |
||||
// if (duration.inMinutes == 1) durationString.write("1 minut"); |
||||
// else durationString.write("${duration.inMinutes} minute"); |
||||
// duration -= Duration(minutes: duration.inMinutes); |
||||
// } |
||||
// |
||||
// return Text( |
||||
// durationString.toString(), |
||||
// style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
// fontSize: isSmallScreen(context) ? 14 : 16, |
||||
// ), |
||||
// textAlign: TextAlign.center, |
||||
// ); |
||||
// }, |
||||
// ), |
||||
// ], |
||||
// ), |
||||
// ), |
||||
// ), |
||||
// ); |
||||
// } |
||||
// } |
||||
|
||||
class DisplayTrainStations extends StatelessWidget { |
||||
final TrainData trainData; |
||||
|
||||
DisplayTrainStations({required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SliverList( |
||||
delegate: SliverChildBuilderDelegate( |
||||
(context, index) { |
||||
return IndexedSemantics( |
||||
child: DisplayTrainStation( |
||||
station: trainData.stations[index], |
||||
), |
||||
index: index, |
||||
); |
||||
}, |
||||
childCount: trainData.stations.length, |
||||
addSemanticIndexes: true, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,476 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:info_tren/models/train_data.dart'; |
||||
import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart' show isSmallScreen; |
||||
|
||||
class DisplayTrainStation extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
DisplayTrainStation({required this.station}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Row( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
Builder( |
||||
builder: (context) { |
||||
final delay = station.departure?.status?.delay ?? station.arrival?.status?.delay; |
||||
final real = station.departure?.status?.real ?? station.arrival?.status?.real; |
||||
|
||||
final isDelayed = delay != null && delay > 0 && real == true; |
||||
final isOnTime = delay != null && delay <= 0 && real == true; |
||||
final isNotScheduled = false; |
||||
|
||||
return KmBadge( |
||||
station: station, |
||||
isNotScheduled: isNotScheduled, |
||||
isDelayed: isDelayed, |
||||
isOnTime: isOnTime, |
||||
); |
||||
} |
||||
), |
||||
Expanded( |
||||
child: Title( |
||||
station: station, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
Time( |
||||
station: station, |
||||
), |
||||
Delay( |
||||
station: station, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class KmBadge extends StatelessWidget { |
||||
final Station station; |
||||
final bool isNotScheduled; |
||||
final bool isOnTime; |
||||
final bool isDelayed; |
||||
|
||||
KmBadge({ |
||||
required this.station, |
||||
this.isNotScheduled = false, |
||||
this.isOnTime = false, |
||||
this.isDelayed = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
Color foregroundColor = Colors.white70; |
||||
Color? backgroundColor; |
||||
|
||||
if (isNotScheduled) { |
||||
foregroundColor = Colors.orange.shade300; |
||||
backgroundColor = Colors.orange.shade900.withOpacity(0.3); |
||||
} |
||||
else if (isOnTime) { |
||||
foregroundColor = Colors.green.shade300; |
||||
backgroundColor = Colors.green.shade900.withOpacity(0.3); |
||||
} |
||||
else if (isDelayed) { |
||||
foregroundColor = Colors.red.shade300; |
||||
backgroundColor = Colors.red.shade900.withOpacity(0.3); |
||||
} |
||||
|
||||
return Padding( |
||||
padding: const EdgeInsets.all(8), |
||||
child: Container( |
||||
decoration: BoxDecoration( |
||||
borderRadius: BorderRadius.circular(10), |
||||
border: Border.all( |
||||
width: 2, |
||||
color: foregroundColor, |
||||
), |
||||
color: backgroundColor, |
||||
), |
||||
width: isSmallScreen(context) ? 42 : 48, |
||||
height: isSmallScreen(context) ? 42 : 48, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: Center( |
||||
child: Text( |
||||
station.km.toString(), |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 16 : 20, |
||||
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, |
||||
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
), |
||||
Text( |
||||
"km", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: 10, |
||||
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Title extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
Title({ |
||||
required this.station |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Text( |
||||
station.name, |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 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; |
||||
|
||||
Time({ |
||||
required this.station, |
||||
}); |
||||
|
||||
@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: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 22, |
||||
), |
||||
), |
||||
Container(width: 2,), |
||||
ArrivalTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
StopTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
DepartureTime(station: station,), |
||||
Container(width: 2,), |
||||
Text( |
||||
"→", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class ArrivalTime extends StatelessWidget { |
||||
final Station station; |
||||
final bool finalStation; |
||||
|
||||
ArrivalTime({ |
||||
required this.station, |
||||
this.finalStation = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (station.arrival == null) { |
||||
return Container(); |
||||
} |
||||
if (finalStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Text( |
||||
"→", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 22, |
||||
), |
||||
), |
||||
Container(width: 2,), |
||||
Text("sosire la "), |
||||
ArrivalTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final delay = station.arrival!.status?.delay ?? 0; |
||||
final time = station.arrival!.scheduleTime; |
||||
|
||||
if (delay == 0) { |
||||
return Text("$time"); |
||||
} |
||||
else if (delay > 0) { |
||||
final splits = time.split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final 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: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
color: Colors.red.shade300, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final splits = time.split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final 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: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
color: Colors.green.shade300, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
class StopTime extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
StopTime({ |
||||
required this.station, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"staționează pentru", |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
int stopsForInt = station.stoppingTime!; |
||||
if (stopsForInt == 1) { |
||||
return Text( |
||||
"1 minut", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else if (stopsForInt < 20) { |
||||
return Text( |
||||
"${station.stoppingTime} minute", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else { |
||||
return Text( |
||||
"${station.stoppingTime} de minute", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
}, |
||||
) |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DepartureTime extends StatelessWidget { |
||||
final Station station; |
||||
final bool firstStation; |
||||
|
||||
DepartureTime({ |
||||
required this.station, |
||||
this.firstStation = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (station.departure == null) { |
||||
return Container(); |
||||
} |
||||
if (firstStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Expanded(child: Container(),), |
||||
Text("plecare la "), |
||||
DepartureTime(station: station,), |
||||
Container(width: 2,), |
||||
Text( |
||||
"→", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final delay = station.departure!.status?.delay ?? 0; |
||||
final time = station.departure!.scheduleTime; |
||||
|
||||
if (delay == 0) { |
||||
return Text("$time"); |
||||
} |
||||
else if (delay > 0) { |
||||
final splits = time.split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final 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: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
color: Colors.red.shade300, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final splits = time.split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final 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: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
color: Colors.green.shade300, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
class Delay extends StatelessWidget { |
||||
final Station station; |
||||
|
||||
Delay({ |
||||
required this.station, |
||||
}); |
||||
|
||||
@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 minute întârziere", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
color: Colors.red.shade300, |
||||
fontSize: 14, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
else if (delay < 0) { |
||||
return Text( |
||||
"${-delay} minute mai devreme", |
||||
style: Theme.of(context).textTheme.bodyText2?.copyWith( |
||||
color: Colors.green.shade300, |
||||
fontSize: 14, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
|
||||
return Container(); |
||||
} |
||||
} |
@ -1,72 +0,0 @@
|
||||
import 'dart:io' show Platform; |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter/cupertino.dart'; |
||||
|
||||
import 'package:info_tren/models/train_data.dart'; |
||||
import 'package:info_tren/train_info_page/train_info_cupertino.dart'; |
||||
import 'package:info_tren/train_info_page/train_info_material.dart'; |
||||
|
||||
mixin TrainInfoMixin { |
||||
String title; |
||||
bool showTrainData; |
||||
TrainLookupResult lookupResult; |
||||
bool requestedData; |
||||
} |
||||
|
||||
class TrainInfo extends StatelessWidget { |
||||
static String routeName = "/trainInfo/display"; |
||||
|
||||
final int trainNumber; |
||||
|
||||
TrainInfo({@required this.trainNumber}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return TrainDataWebViewAdapter( |
||||
builder: (context) { |
||||
if (Platform.isAndroid) { |
||||
return TrainInfoMaterial(trainNumber: trainNumber,); |
||||
} |
||||
else if (Platform.isIOS) { |
||||
return TrainInfoCupertino(trainNumber: trainNumber,); |
||||
} |
||||
|
||||
return null; |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
typedef FutureDisplayCallback<T>(BuildContext context, T data); |
||||
|
||||
class FutureDisplay<T> extends StatelessWidget { |
||||
final Future<T> future; |
||||
final FutureDisplayCallback<T> builder; |
||||
|
||||
FutureDisplay({Key key, @required this.future, @required this.builder}): super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureBuilder( |
||||
future: future, |
||||
builder: (context, snapshot) { |
||||
if (snapshot.hasData) return builder(context, snapshot.data); |
||||
if (snapshot.hasError) throw snapshot.error; |
||||
if (snapshot.connectionState == ConnectionState.done) return Container(); |
||||
|
||||
Widget loadingWidget; |
||||
if (Platform.isAndroid) { |
||||
loadingWidget = CircularProgressIndicator(); |
||||
} |
||||
else if (Platform.isIOS) { |
||||
loadingWidget = CupertinoActivityIndicator(); |
||||
} |
||||
|
||||
return Center( |
||||
child: loadingWidget, |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
@ -1,506 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:info_tren/models/train_data.dart'; |
||||
import 'package:info_tren/train_info_page/train_info.dart'; |
||||
import 'package:info_tren/train_info_page/train_info_constants.dart'; |
||||
|
||||
class DisplayTrainStation extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
DisplayTrainStation({@required this.station}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Row( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
FutureDisplay( |
||||
future: Future.wait([ |
||||
station.delay, |
||||
station.realOrEstimate, |
||||
station.observations, |
||||
]), |
||||
builder: (context, data) { |
||||
final isDelayed = (data[0] as int) > 0 && (data[1] as RealOrEstimate) == RealOrEstimate.real; |
||||
final isOnTime = (data[0] as int) <= 0 && (data[1] as RealOrEstimate) == RealOrEstimate.real; |
||||
final isNotScheduled = data[2] == "ONI"; |
||||
|
||||
return KmBadge( |
||||
station: station, |
||||
isNotScheduled: isNotScheduled, |
||||
isDelayed: isDelayed, |
||||
isOnTime: isOnTime, |
||||
); |
||||
} |
||||
), |
||||
Expanded( |
||||
child: Title( |
||||
station: station, |
||||
), |
||||
) |
||||
], |
||||
), |
||||
Time( |
||||
station: station, |
||||
), |
||||
Delay( |
||||
station: station, |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class KmBadge extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
final bool isNotScheduled; |
||||
final bool isOnTime; |
||||
final bool isDelayed; |
||||
|
||||
KmBadge({ |
||||
@required this.station, |
||||
this.isNotScheduled = false, |
||||
this.isOnTime = false, |
||||
this.isDelayed = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
Color foregroundColor = FOREGROUND_WHITE; |
||||
Color backgroundColor; |
||||
|
||||
if (isNotScheduled) { |
||||
foregroundColor = Color.fromRGBO(225, 175, 30, 1); |
||||
backgroundColor = Color.fromRGBO(80, 40, 10, 1); |
||||
} |
||||
else if (isOnTime) { |
||||
foregroundColor = Color.fromRGBO(130, 175, 65, 1); |
||||
backgroundColor = Color.fromRGBO(40, 80, 10, 1); |
||||
} |
||||
else if (isDelayed) { |
||||
foregroundColor = Color.fromRGBO(225, 75, 30, 1); |
||||
backgroundColor = Color.fromRGBO(80, 20, 10, 1); |
||||
} |
||||
|
||||
return Padding( |
||||
padding: const EdgeInsets.all(8), |
||||
child: Container( |
||||
decoration: BoxDecoration( |
||||
borderRadius: BorderRadius.circular(10), |
||||
border: Border.all( |
||||
width: 2, |
||||
color: foregroundColor, |
||||
), |
||||
color: backgroundColor, |
||||
// color: CupertinoColors.activeOrange, |
||||
), |
||||
width: 48, |
||||
height: 48, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: Center( |
||||
child: FutureDisplay<int>( |
||||
future: station.km, |
||||
builder: (context, value) { |
||||
return Text( |
||||
value.toString(), |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 18, |
||||
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, |
||||
color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
), |
||||
Text( |
||||
"km", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 10, |
||||
color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Title extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
Title({ |
||||
@required this.station |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<String>>( |
||||
future: Future.wait([ |
||||
station.stationName, |
||||
station.observations |
||||
]), |
||||
builder: (context, items) { |
||||
return Text( |
||||
items[0], |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 22, |
||||
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, |
||||
fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Time extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
Time({ |
||||
@required this.station, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<String>>( |
||||
future: Future.wait([ |
||||
station.arrivalTime, |
||||
station.stopsFor, |
||||
station.departureTime, |
||||
]), |
||||
builder: (context, items) { |
||||
if (items[0].isEmpty) { |
||||
// Plecare |
||||
return DepartureTime( |
||||
station: station, |
||||
firstStation: true, |
||||
); |
||||
} |
||||
|
||||
if (items[2].isEmpty) { |
||||
// Sosire |
||||
return ArrivalTime( |
||||
station: station, |
||||
finalStation: true, |
||||
); |
||||
} |
||||
|
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Text( |
||||
"→", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.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: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class ArrivalTime extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
final bool finalStation; |
||||
|
||||
ArrivalTime({ |
||||
@required this.station, |
||||
this.finalStation = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<Object>>( |
||||
future: Future.wait([ |
||||
station.arrivalTime, |
||||
station.delay, |
||||
]), |
||||
builder: (context, data) { |
||||
if (finalStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Text( |
||||
"→", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
Container(width: 2,), |
||||
Text("sosire la "), |
||||
ArrivalTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
if (data[1] == 0) { |
||||
return Text("${data[0]}"); |
||||
} |
||||
else if (data[1] as int > 0) { |
||||
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final oldDate = newDate.subtract(Duration(minutes: data[1] as int)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${data[0]}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.destructiveRed, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final oldDate = newDate.subtract(Duration(minutes: data[1] as int)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${data[0]}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.activeGreen, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class StopTime extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
StopTime({ |
||||
@required this.station, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<String>( |
||||
future: station.stopsFor, |
||||
builder: (context, stopsFor) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"staționează pentru", |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
int stopsForInt = int.parse(stopsFor); |
||||
if (stopsForInt == 1) { |
||||
return Text( |
||||
"1 minut", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else if (stopsForInt < 20) { |
||||
return Text( |
||||
"$stopsFor minute", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else { |
||||
return Text( |
||||
"$stopsFor de minute", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
}, |
||||
) |
||||
], |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DepartureTime extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
final bool firstStation; |
||||
|
||||
DepartureTime({ |
||||
@required this.station, |
||||
this.firstStation = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<Object>>( |
||||
future: Future.wait([ |
||||
station.departureTime, |
||||
station.delay, |
||||
]), |
||||
builder: (context, data) { |
||||
if (firstStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Expanded(child: Container(),), |
||||
Text("plecare la "), |
||||
DepartureTime(station: station,), |
||||
Container(width: 2,), |
||||
Text( |
||||
"→", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
if (data[1] == 0) { |
||||
return Text("${data[0]}"); |
||||
} |
||||
else if (data[1] as int > 0) { |
||||
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final oldDate = newDate.subtract(Duration(minutes: data[1] as int)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${data[0]}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.destructiveRed, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final oldDate = newDate.subtract(Duration(minutes: data[1] as int)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${data[0]}", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.activeGreen, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
|
||||
class Delay extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
Delay({ |
||||
@required this.station, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<int>( |
||||
future: station.delay, |
||||
builder: (context, delay) { |
||||
if (delay == 0) return Container(); |
||||
|
||||
else if (delay > 0) { |
||||
return Text( |
||||
"$delay minute întârziere", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.destructiveRed, |
||||
fontSize: 12, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
|
||||
else if (delay < 0) { |
||||
return Text( |
||||
"${-delay} minute mai devreme", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( |
||||
color: CupertinoColors.activeGreen, |
||||
fontSize: 12, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
|
||||
return Container(); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
@ -1,944 +0,0 @@
|
||||
import 'package:info_tren/train_info_page/train_info_animation_helpers.dart'; |
||||
import 'package:info_tren/train_info_page/train_info_material_DisplayTrainStation.dart'; |
||||
import 'package:info_tren/utils/stream_list.dart'; |
||||
|
||||
import '../models/train_data.dart'; |
||||
import './train_info.dart'; |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
|
||||
class TrainInfoMaterial extends StatefulWidget { |
||||
final int trainNumber; |
||||
|
||||
TrainInfoMaterial({@required this.trainNumber}); |
||||
|
||||
@override |
||||
_TrainInfoMaterialState createState() => _TrainInfoMaterialState(); |
||||
} |
||||
|
||||
class _TrainInfoMaterialState extends State<TrainInfoMaterial> with TrainInfoMixin { |
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
|
||||
title = widget.trainNumber.toString(); |
||||
showTrainData = false; |
||||
requestedData = false; |
||||
} |
||||
|
||||
@override |
||||
void didChangeDependencies() { |
||||
super.didChangeDependencies(); |
||||
|
||||
if (!requestedData) { |
||||
requestedData = true; |
||||
|
||||
TrainDataWebViewAdapter.of(context).loadTrain(widget.trainNumber).then((value) { |
||||
setState(() { |
||||
lookupResult = value; |
||||
}); |
||||
|
||||
if (lookupResult == TrainLookupResult.NOT_FOUND) { |
||||
Future.delayed(Duration(seconds: 5), () { |
||||
Navigator.of(context).pop(); |
||||
}); |
||||
} |
||||
else if (lookupResult == TrainLookupResult.FOUND) { |
||||
Future.delayed(Duration(seconds: 1, milliseconds: 500), () { |
||||
setState(() { |
||||
showTrainData = true; |
||||
}); |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (!showTrainData) { |
||||
return _TrainInfoMaterialBefore( |
||||
title: title, |
||||
lookupResult: lookupResult, |
||||
); |
||||
} |
||||
else { |
||||
return _TrainDataMaterialAfter( |
||||
title: title, |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
class _TrainInfoMaterialBefore extends StatefulWidget { |
||||
final String title; |
||||
final TrainLookupResult lookupResult; |
||||
|
||||
_TrainInfoMaterialBefore({@required this.title, @required this.lookupResult}); |
||||
|
||||
@override |
||||
_TrainInfoMaterialBeforeState createState() => _TrainInfoMaterialBeforeState(); |
||||
} |
||||
|
||||
class _TrainInfoMaterialBeforeState extends State<_TrainInfoMaterialBefore> { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
centerTitle: true, |
||||
title: Text(widget.title ?? ""), |
||||
), |
||||
body: SafeArea( |
||||
bottom: false, |
||||
child: StreamBuilder<ProgressReport>( |
||||
stream: TrainDataWebViewAdapter.of(context).progressStream, |
||||
builder: (context, snapshot) { |
||||
switch (snapshot.connectionState) { |
||||
case ConnectionState.none: |
||||
return Container(); |
||||
case ConnectionState.waiting: |
||||
return Center( |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
CircularProgressIndicator(), |
||||
Text( |
||||
"Conectare...", |
||||
style: Theme.of(context).textTheme.headline6, |
||||
), |
||||
], |
||||
), |
||||
); |
||||
case ConnectionState.active: |
||||
break; |
||||
case ConnectionState.done: |
||||
Navigator.of(context).pop(); |
||||
return Container(); |
||||
} |
||||
|
||||
return Center( |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
ProgressReportDisplayEntry( |
||||
key: ValueKey(1), |
||||
completed: 1 <= snapshot.data.current, |
||||
waitingText: "Se crează WebView", |
||||
completedText: "WebView a fost creat", |
||||
), |
||||
ProgressReportDisplayEntry( |
||||
key: ValueKey(2), |
||||
completed: 2 <= snapshot.data.current, |
||||
waitingText: "Se încarcă pagina Informatica Feroviară", |
||||
completedText: "Pagina Informatica Feroviară a fost încărcată", |
||||
), |
||||
ProgressReportDisplayEntry( |
||||
key: ValueKey(3), |
||||
completed: 3 <= snapshot.data.current, |
||||
waitingText: "Se încarcă informațiile despre tren", |
||||
completedText: "Informațiile despre tren au fost încărcate", |
||||
), |
||||
if (widget.lookupResult != null) |
||||
...[ |
||||
Container(height: 20,), |
||||
SizedBox( |
||||
width: double.infinity, |
||||
child: AnimatedBackground( |
||||
animationDuration: Duration(milliseconds: 250), |
||||
initialColor: Theme.of(context).scaffoldBackgroundColor, |
||||
backgroundColor: |
||||
widget.lookupResult == TrainLookupResult.FOUND |
||||
? Colors.green |
||||
: Colors.red, |
||||
child: Center( |
||||
child: Row( |
||||
children: <Widget>[ |
||||
Expanded(child: Container(),), |
||||
if (widget.lookupResult == TrainLookupResult.FOUND) |
||||
Padding( |
||||
padding: const EdgeInsets.fromLTRB(8, 8, 0, 8), |
||||
child: Center( |
||||
child: CircularProgressIndicator( |
||||
strokeWidth: 2, |
||||
valueColor: AlwaysStoppedAnimation(Colors.greenAccent), |
||||
) |
||||
), |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Text( |
||||
widget.lookupResult == TrainLookupResult.FOUND |
||||
? "Trenul a fost găsit" |
||||
: widget.lookupResult == TrainLookupResult.NOT_FOUND |
||||
? "Trenul nu a fost găsit" |
||||
: "A apărut o eroare în căutarea trenului", |
||||
style: Theme.of(context).textTheme.headline6, |
||||
), |
||||
), |
||||
Expanded(child: Container(),), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
), |
||||
], |
||||
], |
||||
), |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
bool isSmallScreen(BuildContext context) => MediaQuery.of(context).size.height <= 425; |
||||
|
||||
class _TrainDataMaterialAfter extends StatefulWidget { |
||||
final String title; |
||||
|
||||
_TrainDataMaterialAfter({@required this.title}); |
||||
|
||||
@override |
||||
_TrainDataMaterialAfterState createState() => _TrainDataMaterialAfterState(); |
||||
} |
||||
|
||||
class _TrainDataMaterialAfterState extends State<_TrainDataMaterialAfter> { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureBuilder<OnDemandTrainData>( |
||||
future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () { |
||||
Navigator.of(context).pop(); |
||||
}), |
||||
builder: (context, snapshot) { |
||||
if (!snapshot.hasData) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
centerTitle: true, |
||||
title: Text(widget.title ?? ""), |
||||
), |
||||
body: SafeArea( |
||||
child: Center( |
||||
child: CircularProgressIndicator(), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
|
||||
return Scaffold( |
||||
appBar: isSmallScreen(context) ? null : AppBar( |
||||
centerTitle: true, |
||||
title: FutureBuilder<List<String>>( |
||||
future: Future.wait([ |
||||
snapshot.data.rang, |
||||
snapshot.data.trainNumber |
||||
]), |
||||
builder: (context, snapshot) { |
||||
if (snapshot.hasData) { |
||||
return Text("Informații despre ${snapshot.data[0]} ${snapshot.data[1]}"); |
||||
} |
||||
else { |
||||
return Text(widget.title ?? ""); |
||||
} |
||||
}, |
||||
), |
||||
), |
||||
body: Column( |
||||
children: <Widget>[ |
||||
if (isSmallScreen(context)) |
||||
FutureBuilder<List<String>>( |
||||
future: Future.wait([ |
||||
snapshot.data.rang, |
||||
snapshot.data.trainNumber, |
||||
]), |
||||
builder: (context, snapshot) { |
||||
var title = "INFO TREN"; |
||||
if (snapshot.hasData) title = "INFO TREN ─ ${snapshot.data[0]} ${snapshot.data[1]}"; |
||||
|
||||
return SlimAppBar( |
||||
title: title, |
||||
); |
||||
} |
||||
), |
||||
Expanded( |
||||
child: SafeArea( |
||||
bottom: false, |
||||
child: CustomScrollView( |
||||
slivers: <Widget>[ |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainID(trainData: snapshot.data,), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainOperator(trainData: snapshot.data,), |
||||
), |
||||
SliverPadding( |
||||
padding: const EdgeInsets.only(left: 2, right: 2), |
||||
sliver: SliverToBoxAdapter( |
||||
child: DisplayTrainRoute(trainData: snapshot.data,), |
||||
), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainDeparture(trainData: snapshot.data,), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Divider( |
||||
color: Colors.white70, |
||||
height: isSmallScreen(context) ? 8 : 16, |
||||
), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: DisplayTrainLastInfo(trainData: snapshot.data,), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: IntrinsicHeight( |
||||
child: Row( |
||||
children: <Widget>[ |
||||
Expanded(child: DisplayTrainNextStop(trainData: snapshot.data,)), |
||||
Expanded(child: DisplayTrainDestination(trainData: snapshot.data,)), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: IntrinsicHeight( |
||||
child: Row( |
||||
children: <Widget>[ |
||||
Expanded(child: DisplayTrainRouteDuration(trainData: snapshot.data,)), |
||||
Expanded(child: DisplayTrainRouteDistance(trainData: snapshot.data,)), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Divider( |
||||
color: Colors.white70, |
||||
height: isSmallScreen(context) ? 8 : 16, |
||||
), |
||||
), |
||||
DisplayTrainStations( |
||||
trainData: snapshot.data, |
||||
pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture, |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Container( |
||||
height: MediaQuery.of(context).viewPadding.bottom, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainID extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainID({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<String>>( |
||||
future: Future.wait([ |
||||
trainData.rang, |
||||
trainData.trainNumber, |
||||
]), |
||||
builder: (context, list) { |
||||
return Text( |
||||
"${list[0]} ${list[1]}", |
||||
style: (isSmallScreen(context) |
||||
? Theme.of(context).textTheme.headline4 |
||||
: Theme.of(context).textTheme.headline3).copyWith( |
||||
color: Theme.of(context).textTheme.bodyText2.color, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainOperator extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainOperator({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<String>( |
||||
future: trainData.operator, |
||||
builder: (context, op) { |
||||
return Text( |
||||
op, |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontStyle: FontStyle.italic, |
||||
fontSize: isSmallScreen(context) ? 12 : 14, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRoute extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainRoute({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay( |
||||
future: Future.wait([trainData.route.from, trainData.route.to]), |
||||
builder: (context, routePieces) { |
||||
return Row( |
||||
children: <Widget>[ |
||||
Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
routePieces[0], |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Expanded(child: Container(),), |
||||
Center(child: Text("-")), |
||||
Expanded(child: Container(),), |
||||
Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: Text( |
||||
routePieces[1], |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: 16, |
||||
), |
||||
textAlign: TextAlign.right, |
||||
), |
||||
), |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainDeparture extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainDeparture({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: FutureDisplay<DateTime>( |
||||
future: trainData.departureDate, |
||||
builder: (context, dataPlecare) { |
||||
return Text( |
||||
"Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontStyle: FontStyle.italic, |
||||
fontWeight: FontWeight.w200, |
||||
fontSize: isSmallScreen(context) ? 12 : 14, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainLastInfo extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainLastInfo({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
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: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 20, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Row( |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: FutureDisplay( |
||||
future: trainData.lastInfo.station, |
||||
builder: (context, station) { |
||||
return Text( |
||||
station, |
||||
style: Theme.of(context).textTheme.bodyText2, |
||||
textAlign: TextAlign.left, |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
Expanded(child: Container(),), |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: FutureDisplay( |
||||
future: trainData.lastInfo.event, |
||||
builder: (context, event) { |
||||
return Text( |
||||
event, |
||||
style: Theme.of(context).textTheme.bodyText2, |
||||
textAlign: TextAlign.right, |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Row( |
||||
children: <Widget>[ |
||||
FutureDisplay<DateTime>( |
||||
future: trainData.lastInfo.dateAndTime, |
||||
builder: (context, dt) { |
||||
return Text( |
||||
"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, |
||||
); |
||||
}, |
||||
), |
||||
Expanded(child: Container(),), |
||||
FutureBuilder( |
||||
initialData: 0, |
||||
future: trainData.lastInfo.delay, |
||||
builder: (context, snapshot) { |
||||
if (snapshot.data == 0) { |
||||
return Container(); |
||||
} |
||||
|
||||
if (snapshot.data > 0) { |
||||
return Text( |
||||
"${snapshot.data} minute întârziere", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: 14, |
||||
color: Color.fromRGBO(200, 30, 15, 1), |
||||
), |
||||
); |
||||
} |
||||
else { |
||||
return Text( |
||||
"${-snapshot.data} minute mai devreme", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: 12, |
||||
color: Color.fromRGBO(15, 200, 15, 1), |
||||
), |
||||
); |
||||
} |
||||
}, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainNextStop extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainNextStop({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureBuilder( |
||||
future: trainData.nextStop.stationName, |
||||
builder: (context, snapshot) { |
||||
if (!snapshot.hasData) return Container(height: 0,); |
||||
|
||||
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( |
||||
"Următoarea oprire", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 20, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
FutureDisplay( |
||||
future: trainData.nextStop.stationName, |
||||
builder: (context, station) { |
||||
return Padding( |
||||
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), |
||||
child: Text( |
||||
station, |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 16 : 18, |
||||
fontWeight: FontWeight.w500, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
); |
||||
}, |
||||
), |
||||
FutureDisplay<DateTime>( |
||||
future: trainData.nextStop.arrival, |
||||
builder: (context, arrival) { |
||||
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( |
||||
"la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 12 : 14, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
) |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainDestination extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainDestination({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureBuilder( |
||||
future: trainData.destination.stationName, |
||||
builder: (context, snapshot) { |
||||
if (!snapshot.hasData) return Container(height: 0,); |
||||
|
||||
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: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 20, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
FutureDisplay( |
||||
future: trainData.destination.stationName, |
||||
builder: (context, station) { |
||||
return Padding( |
||||
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), |
||||
child: Text( |
||||
station, |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 16 : 18, |
||||
fontWeight: FontWeight.w500, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
); |
||||
}, |
||||
), |
||||
FutureDisplay<DateTime>( |
||||
future: trainData.destination.arrival, |
||||
builder: (context, arrival) { |
||||
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( |
||||
"la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 12 : 14, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
) |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRouteDistance extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainRouteDistance({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"Distanța rutei", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 16 : 18, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
FutureDisplay( |
||||
future: trainData.routeDistance, |
||||
builder: (context, distance) { |
||||
return Text( |
||||
"$distance km", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 14 : 16, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainRouteDuration extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
|
||||
DisplayTrainRouteDuration({@required this.trainData}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"Durata rutei", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 16 : 18, |
||||
fontWeight: FontWeight.bold, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
), |
||||
FutureDisplay<Duration>( |
||||
future: trainData.routeDuration, |
||||
builder: (context, duration) { |
||||
var durationString = StringBuffer(); |
||||
|
||||
bool firstWritten = false; |
||||
|
||||
if (duration.inDays > 0) { |
||||
firstWritten = true; |
||||
if (duration.inDays == 1) durationString.write("1 zi"); |
||||
else durationString.write("${duration.inDays} zile"); |
||||
duration -= Duration(days: duration.inDays); |
||||
} |
||||
|
||||
if (duration.inHours > 0) { |
||||
if (firstWritten) { |
||||
durationString.write(", "); |
||||
} |
||||
firstWritten = true; |
||||
if (duration.inHours == 1) durationString.write("1 oră"); |
||||
else durationString.write("${duration.inHours} ore"); |
||||
duration -= Duration(hours: duration.inHours); |
||||
} |
||||
|
||||
if (duration.inMinutes > 0) { |
||||
if (firstWritten) { |
||||
durationString.write(", "); |
||||
} |
||||
firstWritten = true; |
||||
if (duration.inMinutes == 1) durationString.write("1 minut"); |
||||
else durationString.write("${duration.inMinutes} minute"); |
||||
duration -= Duration(minutes: duration.inMinutes); |
||||
} |
||||
|
||||
return Text( |
||||
durationString.toString(), |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 14 : 16, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DisplayTrainStations extends StatelessWidget { |
||||
final OnDemandTrainData trainData; |
||||
final Future pageLoadFuture; |
||||
|
||||
DisplayTrainStations({@required this.trainData, @required this.pageLoadFuture}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return StreamBuilder<List<OnDemandStation>>( |
||||
stream: listifyStream(trainData.stations(pageLoadFuture: pageLoadFuture)), |
||||
builder: (context, snapshot) { |
||||
if (!snapshot.hasData) { |
||||
return SliverToBoxAdapter( |
||||
child: Container(), |
||||
); |
||||
} |
||||
|
||||
return SliverList( |
||||
delegate: SliverChildBuilderDelegate( |
||||
(context, index) { |
||||
return IndexedSemantics( |
||||
child: DisplayTrainStation( |
||||
station: snapshot.data[index], |
||||
), |
||||
index: index, |
||||
); |
||||
}, |
||||
childCount: snapshot.data.length, |
||||
addSemanticIndexes: true, |
||||
), |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class SlimAppBar extends StatelessWidget { |
||||
final String title; |
||||
final double size; |
||||
// final Function onBackTap; |
||||
|
||||
SlimAppBar({ |
||||
@required this.title, |
||||
this.size = 24, |
||||
// this.onBackTap, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return SizedBox( |
||||
width: double.infinity, |
||||
height: size, |
||||
child: Container( |
||||
color: |
||||
Theme.of(context).appBarTheme?.color ?? |
||||
Theme.of(context).primaryColor, |
||||
child: InkWell( |
||||
onTap: (ModalRoute.of(context)?.canPop ?? false) |
||||
? () => Navigator.of(context).pop() |
||||
: null, |
||||
child: Row( |
||||
mainAxisSize: MainAxisSize.max, |
||||
crossAxisAlignment: CrossAxisAlignment.stretch, |
||||
children: <Widget>[ |
||||
Container( |
||||
height: size, |
||||
width: size, |
||||
child: (ModalRoute.of(context)?.canPop ?? false) |
||||
? BackButtonIcon() |
||||
: null, |
||||
), |
||||
Expanded( |
||||
child: Center( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Text( |
||||
title, |
||||
textAlign: TextAlign.center, |
||||
style: |
||||
Theme.of(context).appBarTheme.textTheme?.caption?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.bodyText2?.color) ?? |
||||
Theme.of(context).textTheme.caption.copyWith(color: Theme.of(context).textTheme.bodyText2.color), |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Container( |
||||
height: size, |
||||
width: size, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -1,509 +0,0 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:info_tren/models/train_data.dart'; |
||||
import 'package:info_tren/train_info_page/train_info.dart'; |
||||
import 'package:info_tren/train_info_page/train_info_material.dart' show isSmallScreen; |
||||
|
||||
class DisplayTrainStation extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
DisplayTrainStation({@required this.station}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(2), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Row( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
FutureDisplay( |
||||
future: Future.wait([ |
||||
station.delay, |
||||
station.realOrEstimate, |
||||
station.observations, |
||||
]), |
||||
builder: (context, data) { |
||||
final isDelayed = (data[0] as int) > 0 && (data[1] as RealOrEstimate) == RealOrEstimate.real; |
||||
final isOnTime = (data[0] as int) <= 0 && (data[1] as RealOrEstimate) == RealOrEstimate.real; |
||||
final isNotScheduled = data[2] == "ONI"; |
||||
|
||||
return KmBadge( |
||||
station: station, |
||||
isNotScheduled: isNotScheduled, |
||||
isDelayed: isDelayed, |
||||
isOnTime: isOnTime, |
||||
); |
||||
} |
||||
), |
||||
Expanded( |
||||
child: Title( |
||||
station: station, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
Time( |
||||
station: station, |
||||
), |
||||
Delay( |
||||
station: station, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class KmBadge extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
final bool isNotScheduled; |
||||
final bool isOnTime; |
||||
final bool isDelayed; |
||||
|
||||
KmBadge({ |
||||
@required this.station, |
||||
this.isNotScheduled = false, |
||||
this.isOnTime = false, |
||||
this.isDelayed = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
Color foregroundColor = Colors.white70; |
||||
Color backgroundColor; |
||||
|
||||
if (isNotScheduled) { |
||||
foregroundColor = Colors.orange.shade300; |
||||
backgroundColor = Colors.orange.shade900.withOpacity(0.3); |
||||
} |
||||
else if (isOnTime) { |
||||
foregroundColor = Colors.green.shade300; |
||||
backgroundColor = Colors.green.shade900.withOpacity(0.3); |
||||
} |
||||
else if (isDelayed) { |
||||
foregroundColor = Colors.red.shade300; |
||||
backgroundColor = Colors.red.shade900.withOpacity(0.3); |
||||
} |
||||
|
||||
return Padding( |
||||
padding: const EdgeInsets.all(8), |
||||
child: Container( |
||||
decoration: BoxDecoration( |
||||
borderRadius: BorderRadius.circular(10), |
||||
border: Border.all( |
||||
width: 2, |
||||
color: foregroundColor, |
||||
), |
||||
color: backgroundColor, |
||||
), |
||||
width: isSmallScreen(context) ? 42 : 48, |
||||
height: isSmallScreen(context) ? 42 : 48, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: Center( |
||||
child: FutureDisplay<int>( |
||||
future: station.km, |
||||
builder: (context, value) { |
||||
return Text( |
||||
value.toString(), |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 14 : 18, |
||||
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, |
||||
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
), |
||||
Text( |
||||
"km", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: 10, |
||||
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Title extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
Title({ |
||||
@required this.station |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<String>>( |
||||
future: Future.wait([ |
||||
station.stationName, |
||||
station.observations |
||||
]), |
||||
builder: (context, items) { |
||||
return Text( |
||||
items[0], |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 22, |
||||
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, |
||||
fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, |
||||
), |
||||
textAlign: TextAlign.center, |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Time extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
Time({ |
||||
@required this.station, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<String>>( |
||||
future: Future.wait([ |
||||
station.arrivalTime, |
||||
station.stopsFor, |
||||
station.departureTime, |
||||
]), |
||||
builder: (context, items) { |
||||
if (items[0].isEmpty) { |
||||
// Plecare |
||||
return DepartureTime( |
||||
station: station, |
||||
firstStation: true, |
||||
); |
||||
} |
||||
|
||||
if (items[2].isEmpty) { |
||||
// Sosire |
||||
return ArrivalTime( |
||||
station: station, |
||||
finalStation: true, |
||||
); |
||||
} |
||||
|
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Text( |
||||
"→", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 22, |
||||
), |
||||
), |
||||
Container(width: 2,), |
||||
ArrivalTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
StopTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
DepartureTime(station: station,), |
||||
Container(width: 2,), |
||||
Text( |
||||
"→", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class ArrivalTime extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
final bool finalStation; |
||||
|
||||
ArrivalTime({ |
||||
@required this.station, |
||||
this.finalStation = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<Object>>( |
||||
future: Future.wait([ |
||||
station.arrivalTime, |
||||
station.delay, |
||||
]), |
||||
builder: (context, data) { |
||||
if (finalStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Text( |
||||
"→", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: isSmallScreen(context) ? 18 : 22, |
||||
), |
||||
), |
||||
Container(width: 2,), |
||||
Text("sosire la "), |
||||
ArrivalTime(station: station,), |
||||
Expanded(child: Container(),), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
if (data[1] == 0) { |
||||
return Text("${data[0]}"); |
||||
} |
||||
else if (data[1] as int > 0) { |
||||
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final oldDate = newDate.subtract(Duration(minutes: data[1] as int)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${data[0]}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
color: Colors.red.shade300, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final oldDate = newDate.subtract(Duration(minutes: data[1] as int)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${data[0]}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
color: Colors.green.shade300, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class StopTime extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
StopTime({ |
||||
@required this.station, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<String>( |
||||
future: station.stopsFor, |
||||
builder: (context, stopsFor) { |
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"staționează pentru", |
||||
textAlign: TextAlign.center, |
||||
), |
||||
Builder( |
||||
builder: (context) { |
||||
int stopsForInt = int.parse(stopsFor); |
||||
if (stopsForInt == 1) { |
||||
return Text( |
||||
"1 minut", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else if (stopsForInt < 20) { |
||||
return Text( |
||||
"$stopsFor minute", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
else { |
||||
return Text( |
||||
"$stopsFor de minute", |
||||
textAlign: TextAlign.center, |
||||
); |
||||
} |
||||
}, |
||||
) |
||||
], |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class DepartureTime extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
final bool firstStation; |
||||
|
||||
DepartureTime({ |
||||
@required this.station, |
||||
this.firstStation = false, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<List<Object>>( |
||||
future: Future.wait([ |
||||
station.departureTime, |
||||
station.delay, |
||||
]), |
||||
builder: (context, data) { |
||||
if (firstStation) { |
||||
return Row( |
||||
crossAxisAlignment: CrossAxisAlignment.center, |
||||
children: <Widget>[ |
||||
Expanded(child: Container(),), |
||||
Text("plecare la "), |
||||
DepartureTime(station: station,), |
||||
Container(width: 2,), |
||||
Text( |
||||
"→", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
fontSize: 22, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
if (data[1] == 0) { |
||||
return Text("${data[0]}"); |
||||
} |
||||
else if (data[1] as int > 0) { |
||||
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final oldDate = newDate.subtract(Duration(minutes: data[1] as int)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${data[0]}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
color: Colors.red.shade300, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
else { |
||||
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList(); |
||||
|
||||
final now = DateTime.now(); |
||||
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); |
||||
final oldDate = newDate.subtract(Duration(minutes: data[1] as int)); |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text( |
||||
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
decoration: TextDecoration.lineThrough, |
||||
), |
||||
), |
||||
Text( |
||||
"${data[0]}", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
color: Colors.green.shade300, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
|
||||
|
||||
class Delay extends StatelessWidget { |
||||
final OnDemandStation station; |
||||
|
||||
Delay({ |
||||
@required this.station, |
||||
}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return FutureDisplay<int>( |
||||
future: station.delay, |
||||
builder: (context, delay) { |
||||
if (delay == 0) return Container(); |
||||
|
||||
else if (delay > 0) { |
||||
return Text( |
||||
"$delay minute întârziere", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
color: Colors.red.shade300, |
||||
fontSize: 12, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
|
||||
else if (delay < 0) { |
||||
return Text( |
||||
"${-delay} minute mai devreme", |
||||
style: Theme.of(context).textTheme.bodyText2.copyWith( |
||||
color: Colors.green.shade300, |
||||
fontSize: 12, |
||||
fontStyle: FontStyle.italic, |
||||
), |
||||
); |
||||
} |
||||
|
||||
return Container(); |
||||
}, |
||||
); |
||||
} |
||||
} |
@ -1,385 +0,0 @@
|
||||
import 'dart:convert'; |
||||
import 'dart:io' show Platform; |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:flutter/services.dart'; |
||||
import 'package:info_tren/train_info_page/train_info.dart'; |
||||
import 'package:json_annotation/json_annotation.dart'; |
||||
import 'package:tuple/tuple.dart'; |
||||
|
||||
part 'train_info_prompt.g.dart'; |
||||
|
||||
typedef TrainSelectedCallback(int trainNumber); |
||||
|
||||
mixin TrainInfoPromptCommon { |
||||
static String routeName = "/trainInfo/chooseTrain"; |
||||
|
||||
onTrainSelected(BuildContext context, int selection) { |
||||
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: selection); |
||||
} |
||||
} |
||||
|
||||
mixin TrainInfoPromptListHandling { |
||||
List<TrainOperatorLines> operators = []; |
||||
|
||||
Future loadOperators(BuildContext context) async { |
||||
operators = []; |
||||
|
||||
final operatorsString = await DefaultAssetBundle.of(context).loadString("assets/lines/files.txt"); |
||||
final operatorsFilesList = operatorsString.split("\n"); |
||||
|
||||
final decoder = JsonDecoder(); |
||||
|
||||
for (final operatorFile in operatorsFilesList) { |
||||
final operatorString = await DefaultAssetBundle.of(context).loadString("assets/lines/$operatorFile"); |
||||
final operatorData = decoder.convert(operatorString); |
||||
final _operator = TrainOperatorLines.fromJson(operatorData); |
||||
operators.add(_operator); |
||||
} |
||||
} |
||||
|
||||
Widget getOperatorsListView(BuildContext context, {String currentInput = "", @required TrainSelectedCallback onTrainSelected}) { |
||||
var sliversTuple = operators.map( |
||||
(op) => Tuple2( |
||||
getFilteredLines(op, currentInput), |
||||
getOperatorSliver(context, op, currentInput, onTrainSelected) |
||||
) |
||||
) |
||||
.where((tuple) => tuple.item1.isNotEmpty).toList(); |
||||
if (currentInput.isNotEmpty) sliversTuple.sort((a, b) { |
||||
final aTrain = a.item1.first; |
||||
final bTrain = b.item1.first; |
||||
|
||||
final inputAsRegExp = RegExp(currentInput); |
||||
|
||||
final matchOnA = inputAsRegExp.firstMatch(aTrain.number); |
||||
final matchOnB = inputAsRegExp.firstMatch(bTrain.number); |
||||
|
||||
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start; |
||||
|
||||
if (aTrain.number.length != bTrain.number.length) return aTrain.number.length - bTrain.number.length; |
||||
|
||||
return aTrain.number.compareTo(bTrain.number); |
||||
}); |
||||
var slivers = sliversTuple.map((tuple) => tuple.item2).toList(); |
||||
|
||||
return CustomScrollView( |
||||
slivers: <Widget>[ |
||||
...slivers, |
||||
SliverToBoxAdapter( |
||||
child: getUseCurrentInputWidget(currentInput, onTrainSelected), |
||||
), |
||||
SliverToBoxAdapter( |
||||
child: Container( |
||||
height: MediaQuery.of(context).viewPadding.bottom, |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
|
||||
Widget getUseCurrentInputWidget(String currentInput, TrainSelectedCallback onTrainSelected) { |
||||
if (currentInput.isEmpty) { |
||||
return Container(); |
||||
} |
||||
|
||||
if (int.tryParse(currentInput) == null) { |
||||
return Container(); |
||||
} |
||||
|
||||
return Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
if (Platform.isAndroid) |
||||
ListTile( |
||||
title: Text("Caută trenul cu numărul $currentInput"), |
||||
onTap: () { |
||||
onTrainSelected(int.parse(currentInput)); |
||||
}, |
||||
) |
||||
else if (Platform.isIOS) |
||||
GestureDetector( |
||||
onTap: () { |
||||
onTrainSelected(int.parse(currentInput)); |
||||
}, |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(8), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: <Widget>[ |
||||
Text("Caută trenul cu numărul $currentInput") |
||||
], |
||||
) |
||||
), |
||||
), |
||||
Divider(), |
||||
], |
||||
); |
||||
} |
||||
|
||||
List<_TrainOperatorTrainDescription> getFilteredLines(TrainOperatorLines _operator, String currentInput) { |
||||
if (currentInput.isNotEmpty) { |
||||
final filteredLines = _operator.trains |
||||
.where((elem) => elem.number.contains(currentInput)) |
||||
.toList(); |
||||
|
||||
filteredLines.sort((a, b) { |
||||
final inputAsRegExp = RegExp(currentInput); |
||||
|
||||
final matchOnA = inputAsRegExp.firstMatch(a.number); |
||||
final matchOnB = inputAsRegExp.firstMatch(b.number); |
||||
|
||||
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start; |
||||
|
||||
if (a.number.length != b.number.length) return a.number.length - b.number.length; |
||||
|
||||
return a.number.compareTo(b.number); |
||||
}); |
||||
|
||||
return filteredLines; |
||||
} |
||||
else { |
||||
return _operator.trains; |
||||
} |
||||
} |
||||
|
||||
Widget getOperatorSliver(BuildContext context, TrainOperatorLines _operator, String currentInput, TrainSelectedCallback onTrainSelected) { |
||||
final filteredLines = getFilteredLines(_operator, currentInput); |
||||
|
||||
if (filteredLines.isEmpty) { |
||||
return SliverToBoxAdapter(child: Container(),); |
||||
} |
||||
|
||||
return SliverPrototypeExtentList( |
||||
prototypeItem: Column( |
||||
children: <Widget>[ |
||||
getLineListItem( |
||||
context, |
||||
op: TrainOperatorLines(), |
||||
line: _TrainOperatorTrainDescription() |
||||
), |
||||
Divider(), |
||||
], |
||||
), |
||||
delegate: SliverChildBuilderDelegate( |
||||
(context, index) { |
||||
return Column( |
||||
children: <Widget>[ |
||||
getLineListItem( |
||||
context, |
||||
op: _operator, |
||||
line: filteredLines[index], |
||||
onTrainSelected: onTrainSelected |
||||
), |
||||
Divider(), |
||||
], |
||||
); |
||||
}, |
||||
childCount: filteredLines.length, |
||||
addSemanticIndexes: true, |
||||
), |
||||
); |
||||
} |
||||
|
||||
Widget getLineListItem(BuildContext context, {TrainOperatorLines op, _TrainOperatorTrainDescription line, TrainSelectedCallback onTrainSelected}) { |
||||
if (Platform.isAndroid) { |
||||
return ListTile( |
||||
dense: true, |
||||
title: Text("${line.rang ?? ""} ${line.number ?? ""}"), |
||||
subtitle: Text(op.operator ?? ""), |
||||
onTap: () { |
||||
onTrainSelected(line.internalNumber); |
||||
}, |
||||
); |
||||
} |
||||
else if (Platform.isIOS) { |
||||
return GestureDetector( |
||||
onTap: () { |
||||
onTrainSelected(line.internalNumber); |
||||
}, |
||||
child: Padding( |
||||
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2), |
||||
child: SizedBox( |
||||
width: double.infinity, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.stretch, |
||||
children: <Widget>[ |
||||
Text( |
||||
op.operator ?? "", |
||||
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200), |
||||
textAlign: TextAlign.left, |
||||
), |
||||
Text( |
||||
"${line.rang ?? ""} ${line.number ?? ""}", |
||||
textAlign: TextAlign.left, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
||||
|
||||
class TrainInfoPromptMaterial extends StatefulWidget { |
||||
@override |
||||
_TrainInfoPromptMaterialState createState() => _TrainInfoPromptMaterialState(); |
||||
} |
||||
|
||||
class _TrainInfoPromptMaterialState extends State<TrainInfoPromptMaterial> with TrainInfoPromptCommon, TrainInfoPromptListHandling { |
||||
TextEditingController trainNoController = TextEditingController(); |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
|
||||
loadOperators(context).then((_) { |
||||
setState(() {}); |
||||
}); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: Text("Informații despre tren"), |
||||
centerTitle: true, |
||||
), |
||||
body: SafeArea( |
||||
bottom: false, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: TextField( |
||||
controller: trainNoController, |
||||
autofocus: true, |
||||
decoration: InputDecoration( |
||||
border: OutlineInputBorder(), |
||||
labelText: "Numărul trenului", |
||||
), |
||||
inputFormatters: [ |
||||
FilteringTextInputFormatter.digitsOnly, |
||||
], |
||||
textInputAction: TextInputAction.search, |
||||
keyboardType: TextInputType.number, |
||||
onChanged: (_) { |
||||
setState(() {}); |
||||
}, |
||||
), |
||||
), |
||||
Expanded( |
||||
child: getOperatorsListView(context, currentInput: trainNoController.text, onTrainSelected: (number) { |
||||
onTrainSelected(context, number); |
||||
}) |
||||
) |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class TrainInfoPromptCupertino extends StatefulWidget { |
||||
@override |
||||
_TrainInfoPromptCupertinoState createState() => _TrainInfoPromptCupertinoState(); |
||||
} |
||||
|
||||
class _TrainInfoPromptCupertinoState extends State<TrainInfoPromptCupertino> with TrainInfoPromptCommon, TrainInfoPromptListHandling { |
||||
TextEditingController trainNoController = TextEditingController(); |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
|
||||
loadOperators(context).then((_) { |
||||
setState(() {}); |
||||
}); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return CupertinoPageScaffold( |
||||
navigationBar: CupertinoNavigationBar( |
||||
middle: Text("Informații despre tren"), |
||||
), |
||||
child: SafeArea( |
||||
bottom: false, |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.max, |
||||
children: <Widget>[ |
||||
Padding( |
||||
padding: const EdgeInsets.all(4), |
||||
child: CupertinoTextField( |
||||
controller: trainNoController, |
||||
autofocus: true, |
||||
placeholder: "Numărul trenului", |
||||
textInputAction: TextInputAction.search, |
||||
keyboardType: TextInputType.number, |
||||
onChanged: (_) { |
||||
setState(() {}); |
||||
}, |
||||
inputFormatters: [ |
||||
FilteringTextInputFormatter.digitsOnly, |
||||
], |
||||
), |
||||
), |
||||
Expanded( |
||||
child: getOperatorsListView( |
||||
context, |
||||
currentInput: trainNoController.text, onTrainSelected: (number) { |
||||
onTrainSelected(context, number); |
||||
} |
||||
) |
||||
) |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
@JsonSerializable() |
||||
class TrainOperatorLines { |
||||
@JsonKey(name: "short_name") |
||||
final String shortName; |
||||
final String operator; |
||||
@JsonKey(name: "versiune") |
||||
final String version; |
||||
@JsonKey(name: "trenuri") |
||||
final List<_TrainOperatorTrainDescription> trains; |
||||
|
||||
TrainOperatorLines({ |
||||
this.operator, |
||||
this.shortName = "", |
||||
this.version, |
||||
this.trains, |
||||
}); |
||||
|
||||
factory TrainOperatorLines.fromJson(Map<String, dynamic> json) => _$TrainOperatorLinesFromJson(json); |
||||
Map<String, dynamic> toJson() => _$TrainOperatorLinesToJson(this); |
||||
} |
||||
|
||||
@JsonSerializable() |
||||
class _TrainOperatorTrainDescription { |
||||
final String rang; |
||||
@JsonKey(name: "numar") |
||||
final String number; |
||||
@JsonKey(name: "numar_intern") |
||||
final int internalNumber; |
||||
|
||||
_TrainOperatorTrainDescription({ |
||||
this.number, |
||||
this.rang, |
||||
this.internalNumber |
||||
}); |
||||
|
||||
factory _TrainOperatorTrainDescription.fromJson(Map<String, dynamic> json) => _$_TrainOperatorTrainDescriptionFromJson(json); |
||||
Map<String, dynamic> toJson() => _$_TrainOperatorTrainDescriptionToJson(this); |
||||
} |
@ -1,44 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||
|
||||
part of 'train_info_prompt.dart'; |
||||
|
||||
// ************************************************************************** |
||||
// JsonSerializableGenerator |
||||
// ************************************************************************** |
||||
|
||||
TrainOperatorLines _$TrainOperatorLinesFromJson(Map<String, dynamic> json) { |
||||
return TrainOperatorLines( |
||||
operator: json['operator'] as String, |
||||
shortName: json['short_name'] as String, |
||||
version: json['versiune'] as String, |
||||
trains: (json['trenuri'] as List) |
||||
?.map((e) => e == null |
||||
? null |
||||
: _TrainOperatorTrainDescription.fromJson( |
||||
e as Map<String, dynamic>)) |
||||
?.toList()); |
||||
} |
||||
|
||||
Map<String, dynamic> _$TrainOperatorLinesToJson(TrainOperatorLines instance) => |
||||
<String, dynamic>{ |
||||
'short_name': instance.shortName, |
||||
'operator': instance.operator, |
||||
'versiune': instance.version, |
||||
'trenuri': instance.trains |
||||
}; |
||||
|
||||
_TrainOperatorTrainDescription _$_TrainOperatorTrainDescriptionFromJson( |
||||
Map<String, dynamic> json) { |
||||
return _TrainOperatorTrainDescription( |
||||
number: json['numar'] as String, |
||||
rang: json['rang'] as String, |
||||
internalNumber: json['numar_intern'] as int); |
||||
} |
||||
|
||||
Map<String, dynamic> _$_TrainOperatorTrainDescriptionToJson( |
||||
_TrainOperatorTrainDescription instance) => |
||||
<String, dynamic>{ |
||||
'rang': instance.rang, |
||||
'numar': instance.number, |
||||
'numar_intern': instance.internalNumber |
||||
}; |
@ -0,0 +1,12 @@
|
||||
import 'dart:io'; |
||||
|
||||
import 'package:info_tren/models/ui_design.dart'; |
||||
|
||||
UiDesign get defaultUiDesign { |
||||
if (Platform.isIOS) { |
||||
return UiDesign.CUPERTINO; |
||||
} |
||||
else { |
||||
return UiDesign.MATERIAL; |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
import 'package:info_tren/models/train_data.dart'; |
||||
|
||||
String stateToString(State state) { |
||||
switch(state) { |
||||
case State.PASSING: |
||||
return 'trecere fără oprire'; |
||||
case State.ARRIVAL: |
||||
return 'sosire'; |
||||
case State.DEPARTURE: |
||||
return 'plecare'; |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
extension TakeWhile on String { |
||||
String takeWhile(Function charValidator) { |
||||
StringBuffer output = StringBuffer(); |
||||
|
||||
for (final char in this.codeUnits) { |
||||
if (charValidator(char)) output.writeCharCode(char); |
||||
else break; |
||||
} |
||||
|
||||
return output.toString(); |
||||
} |
||||
} |
@ -1,34 +0,0 @@
|
||||
import 'dart:convert'; |
||||
import 'dart:io' show Platform; |
||||
|
||||
import 'package:flutter/foundation.dart'; |
||||
import 'package:webview_flutter/webview_flutter.dart'; |
||||
|
||||
/// Evaluates a JavaScript function on the given WebView. |
||||
/// |
||||
/// The JavaScript function must return a String. |
||||
/// |
||||
/// On Android, the `String` resulted from the evaluation |
||||
/// is JSON parsed. On iOS, the `String` is returned as is. |
||||
/// |
||||
/// Other platforms are not supported. The returned value |
||||
/// in this case will be `null`. |
||||
Future<String> wInvoke({ |
||||
@required WebViewController webViewController, |
||||
@required String jsFunctionContent, |
||||
bool isFunctionAlready = false |
||||
}) async { |
||||
final actualJS = isFunctionAlready ? |
||||
jsFunctionContent : |
||||
""" |
||||
(() => { |
||||
$jsFunctionContent |
||||
})() |
||||
"""; |
||||
|
||||
final res = await webViewController.evaluateJavascript(actualJS); |
||||
|
||||
if (Platform.isAndroid) return JsonDecoder().convert(res) as String; |
||||
else if (Platform.isIOS) return res; |
||||
else return null; |
||||
} |
After Width: | Height: | Size: 917 B |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<!-- |
||||
If you are serving your web app in a path other than the root, change the |
||||
href value below to reflect the base path you are serving from. |
||||
|
||||
The path provided below has to start and end with a slash "/" in order for |
||||
it to work correctly. |
||||
|
||||
For more details: |
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base |
||||
|
||||
This is a placeholder for base href that will be replaced by the value of |
||||
the `--base-href` argument provided to `flutter build`. |
||||
--> |
||||
<base href="$FLUTTER_BASE_HREF"> |
||||
|
||||
<meta charset="UTF-8"> |
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> |
||||
<meta name="description" content="A new Flutter project."> |
||||
|
||||
<!-- iOS meta tags & icons --> |
||||
<meta name="apple-mobile-web-app-capable" content="yes"> |
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black"> |
||||
<meta name="apple-mobile-web-app-title" content="info_tren"> |
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png"> |
||||
|
||||
<title>info_tren</title> |
||||
<link rel="manifest" href="manifest.json"> |
||||
</head> |
||||
<body> |
||||
<!-- This script installs service_worker.js to provide PWA functionality to |
||||
application. For more information, see: |
||||
https://developers.google.com/web/fundamentals/primers/service-workers --> |
||||
<script> |
||||
var serviceWorkerVersion = null; |
||||
var scriptLoaded = false; |
||||
function loadMainDartJs() { |
||||
if (scriptLoaded) { |
||||
return; |
||||
} |
||||
scriptLoaded = true; |
||||
var scriptTag = document.createElement('script'); |
||||
scriptTag.src = 'main.dart.js'; |
||||
scriptTag.type = 'application/javascript'; |
||||
document.body.append(scriptTag); |
||||
} |
||||
|
||||
if ('serviceWorker' in navigator) { |
||||
// Service workers are supported. Use them. |
||||
window.addEventListener('load', function () { |
||||
// Wait for registration to finish before dropping the <script> tag. |
||||
// Otherwise, the browser will load the script multiple times, |
||||
// potentially different versions. |
||||
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion; |
||||
navigator.serviceWorker.register(serviceWorkerUrl) |
||||
.then((reg) => { |
||||
function waitForActivation(serviceWorker) { |
||||
serviceWorker.addEventListener('statechange', () => { |
||||
if (serviceWorker.state == 'activated') { |
||||
console.log('Installed new service worker.'); |
||||
loadMainDartJs(); |
||||
} |
||||
}); |
||||
} |
||||
if (!reg.active && (reg.installing || reg.waiting)) { |
||||
// No active web worker and we have installed or are installing |
||||
// one for the first time. Simply wait for it to activate. |
||||
waitForActivation(reg.installing || reg.waiting); |
||||
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) { |
||||
// When the app updates the serviceWorkerVersion changes, so we |
||||
// need to ask the service worker to update. |
||||
console.log('New service worker available.'); |
||||
reg.update(); |
||||
waitForActivation(reg.installing); |
||||
} else { |
||||
// Existing service worker is still good. |
||||
console.log('Loading app from service worker.'); |
||||
loadMainDartJs(); |
||||
} |
||||
}); |
||||
|
||||
// If service worker doesn't succeed in a reasonable amount of time, |
||||
// fallback to plaint <script> tag. |
||||
setTimeout(() => { |
||||
if (!scriptLoaded) { |
||||
console.warn( |
||||
'Failed to load app from service worker. Falling back to plain <script> tag.', |
||||
); |
||||
loadMainDartJs(); |
||||
} |
||||
}, 4000); |
||||
}); |
||||
} else { |
||||
// Service workers not supported. Just drop the <script> tag. |
||||
loadMainDartJs(); |
||||
} |
||||
</script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,35 @@
|
||||
{ |
||||
"name": "info_tren", |
||||
"short_name": "info_tren", |
||||
"start_url": ".", |
||||
"display": "standalone", |
||||
"background_color": "#0175C2", |
||||
"theme_color": "#0175C2", |
||||
"description": "A new Flutter project.", |
||||
"orientation": "portrait-primary", |
||||
"prefer_related_applications": false, |
||||
"icons": [ |
||||
{ |
||||
"src": "icons/Icon-192.png", |
||||
"sizes": "192x192", |
||||
"type": "image/png" |
||||
}, |
||||
{ |
||||
"src": "icons/Icon-512.png", |
||||
"sizes": "512x512", |
||||
"type": "image/png" |
||||
}, |
||||
{ |
||||
"src": "icons/Icon-maskable-192.png", |
||||
"sizes": "192x192", |
||||
"type": "image/png", |
||||
"purpose": "maskable" |
||||
}, |
||||
{ |
||||
"src": "icons/Icon-maskable-512.png", |
||||
"sizes": "512x512", |
||||
"type": "image/png", |
||||
"purpose": "maskable" |
||||
} |
||||
] |
||||
} |