@ -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 |
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"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<resources> |
<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 |
<!-- Show a splash screen on the activity. Automatically removed when |
||||||
Flutter draws its first frame --> |
Flutter draws its first frame --> |
||||||
<item name="android:windowBackground">@drawable/launch_background</item> |
<item name="android:windowBackground">@drawable/launch_background</item> |
||||||
</style> |
</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> |
</resources> |
||||||
|
@ -1,2 +1,3 @@ |
|||||||
org.gradle.jvmargs=-Xmx1536M |
org.gradle.jvmargs=-Xmx1536M |
||||||
|
android.useAndroidX=true |
||||||
|
android.enableJetifier=true |
||||||
|
@ -1,15 +1,11 @@ |
|||||||
include ':app' |
include ':app' |
||||||
|
|
||||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() |
def localPropertiesFile = new File(rootProject.projectDir, "local.properties") |
||||||
|
def properties = new Properties() |
||||||
|
|
||||||
def plugins = new Properties() |
assert localPropertiesFile.exists() |
||||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') |
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } |
||||||
if (pluginsFile.exists()) { |
|
||||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } |
|
||||||
} |
|
||||||
|
|
||||||
plugins.each { name, path -> |
def flutterSdkPath = properties.getProperty("flutter.sdk") |
||||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() |
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" |
||||||
include ":$name" |
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" |
||||||
project(":$name").projectDir = pluginDirectory |
|
||||||
} |
|
||||||
|
@ -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" |
#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" |
#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 |
@ -1 +1 @@ |
|||||||
#import "GeneratedPluginRegistrant.h" |
#import "GeneratedPluginRegistrant.h" |
||||||
|
@ -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/material.dart'; |
||||||
import 'package:flutter/cupertino.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; |
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" |
||||||
|
} |
||||||
|
] |
||||||
|
} |