diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT
index 3d1aef6..37dfd83 100644
--- a/CHANGELOG.TXT
+++ b/CHANGELOG.TXT
@@ -1,3 +1,8 @@
+v2.0.7
+Switched from WebView to API
+Updated app to latest Flutter
+Tweaks
+
v2.0.6
Brought feature parity with iOS _(except for v2.0.2, which is iOS specific)_.
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/analysis_options.yaml
@@ -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
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/android/.gitignore
@@ -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
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 3f9921f..5dd6841 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -38,7 +38,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "ml.dandevelop.info_tren"
+ applicationId "xyz.dcdevelop.infotren"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
diff --git a/android/app/src/main/kotlin/xyz/dcdevelop/info_tren/MainActivity.kt b/android/app/src/main/kotlin/xyz/dcdevelop/info_tren/MainActivity.kt
new file mode 100644
index 0000000..0fd345f
--- /dev/null
+++ b/android/app/src/main/kotlin/xyz/dcdevelop/info_tren/MainActivity.kt
@@ -0,0 +1,6 @@
+package xyz.dcdevelop.info_tren
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..449a9f9
--- /dev/null
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/fonts/ah/ah-Bold.ttf b/fonts/ah/ah-Bold.ttf
new file mode 100644
index 0000000..14b7196
Binary files /dev/null and b/fonts/ah/ah-Bold.ttf differ
diff --git a/fonts/ah/ah-BoldItalic.ttf b/fonts/ah/ah-BoldItalic.ttf
new file mode 100644
index 0000000..4532705
Binary files /dev/null and b/fonts/ah/ah-BoldItalic.ttf differ
diff --git a/fonts/ah/ah-Italic.ttf b/fonts/ah/ah-Italic.ttf
new file mode 100644
index 0000000..89e5ce4
Binary files /dev/null and b/fonts/ah/ah-Italic.ttf differ
diff --git a/fonts/ah/ah-Regular.ttf b/fonts/ah/ah-Regular.ttf
new file mode 100644
index 0000000..c4fa6fb
Binary files /dev/null and b/fonts/ah/ah-Regular.ttf differ
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 0000000..151026b
--- /dev/null
+++ b/ios/.gitignore
@@ -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
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index 6b4c0f7..8d4492f 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -3,7 +3,7 @@
CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
+ en
CFBundleExecutable
App
CFBundleIdentifier
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 8.0
+ 9.0
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
index e8efba1..592ceee 100644
--- a/ios/Flutter/Debug.xcconfig
+++ b/ios/Flutter/Debug.xcconfig
@@ -1,2 +1 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Flutter/Flutter.podspec b/ios/Flutter/Flutter.podspec
deleted file mode 100644
index 2c4421c..0000000
--- a/ios/Flutter/Flutter.podspec
+++ /dev/null
@@ -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
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
index 399e934..592ceee 100644
--- a/ios/Flutter/Release.xcconfig
+++ b/ios/Flutter/Release.xcconfig
@@ -1,2 +1 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh
index eafe31c..2d03b62 100755
--- a/ios/Flutter/flutter_export_environment.sh
+++ b/ios/Flutter/flutter_export_environment.sh
@@ -5,7 +5,6 @@ export "FLUTTER_APPLICATION_PATH=/Users/dan.cojocaru/info_tren"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=/Users/dan.cojocaru/info_tren/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
-export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "FLUTTER_BUILD_NAME=2.0.6"
export "FLUTTER_BUILD_NUMBER=2.0.6"
export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ=="
diff --git a/ios/Podfile b/ios/Podfile
deleted file mode 100644
index 1e8c3c9..0000000
--- a/ios/Podfile
+++ /dev/null
@@ -1,41 +0,0 @@
-# Uncomment this line to define a global platform for your project
-# platform :ios, '9.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def flutter_root
- generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
- unless File.exist?(generated_xcode_build_settings_path)
- raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
- end
-
- File.foreach(generated_xcode_build_settings_path) do |line|
- matches = line.match(/FLUTTER_ROOT\=(.*)/)
- return matches[1].strip if matches
- end
- raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_ios_podfile_setup
-
-target 'Runner' do
- use_frameworks!
- use_modular_headers!
-
- flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
-end
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_ios_build_settings(target)
- end
-end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
deleted file mode 100644
index 5a029b1..0000000
--- a/ios/Podfile.lock
+++ /dev/null
@@ -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
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 7fbc945..4a5186f 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -3,15 +3,13 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 722F441253D3B79676E4DE80 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F72320B12B1F4015789BBC8E /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
- 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -33,12 +31,9 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 313F1E773DA06364A0C4F20A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 636963D381657D3BAEDC0A47 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 74CD890ACD2E394E606FCBEB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
@@ -47,7 +42,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- F72320B12B1F4015789BBC8E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -55,21 +49,12 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 722F441253D3B79676E4DE80 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 0B24EBF53F1DCC708FA961FD /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- F72320B12B1F4015789BBC8E /* Pods_Runner.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -87,8 +72,6 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
- A2E7A2EB20EFBBAC4AB0299B /* Pods */,
- 0B24EBF53F1DCC708FA961FD /* Frameworks */,
);
sourceTree = "";
};
@@ -107,7 +90,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
- 97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@@ -116,23 +98,6 @@
path = Runner;
sourceTree = "";
};
- 97C146F11CF9000F007C117D /* Supporting Files */ = {
- isa = PBXGroup;
- children = (
- );
- name = "Supporting Files";
- sourceTree = "";
- };
- A2E7A2EB20EFBBAC4AB0299B /* Pods */ = {
- isa = PBXGroup;
- children = (
- 313F1E773DA06364A0C4F20A /* Pods-Runner.debug.xcconfig */,
- 74CD890ACD2E394E606FCBEB /* Pods-Runner.release.xcconfig */,
- 636963D381657D3BAEDC0A47 /* Pods-Runner.profile.xcconfig */,
- );
- path = Pods;
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -140,14 +105,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 0D525F98970BF5A8EFFD825C /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- 1B7EDCF8AB293318D8391906 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -165,18 +128,16 @@
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
- ORGANIZATIONNAME = "The Chromium Authors";
+ ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
- DevelopmentTeam = NF9A3KMT8Q;
- LastSwiftMigration = 0910;
- ProvisioningStyle = Automatic;
+ LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 3.2";
+ compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -200,7 +161,6 @@
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
- 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
@@ -209,46 +169,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 0D525F98970BF5A8EFFD825C /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 1B7EDCF8AB293318D8391906 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/webview_flutter/webview_flutter.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/webview_flutter.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -352,9 +272,10 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -365,27 +286,19 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = NF9A3KMT8Q;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = 2.0.7;
ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
+ LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
- "$(PROJECT_DIR)/Flutter",
+ "@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren;
+ MARKETING_VERSION = 2.0.7;
+ PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren;
PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
@@ -437,7 +350,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -486,10 +399,12 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -501,29 +416,19 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = NF9A3KMT8Q;
+ CURRENT_PROJECT_VERSION = 2.0.7;
ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
+ LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
- "$(PROJECT_DIR)/Flutter",
+ "@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren;
+ MARKETING_VERSION = 2.0.7;
+ PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren;
PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -534,28 +439,18 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = NF9A3KMT8Q;
+ CURRENT_PROJECT_VERSION = 2.0.7;
ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
+ LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
- "$(PROJECT_DIR)/Flutter",
+ "@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren;
+ MARKETING_VERSION = 2.0.7;
+ PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren;
PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
index 21a3cc1..1d526a1 100644
--- a/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,7 +4,4 @@
-
-
diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index 71cc41e..70693e4 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -5,7 +5,7 @@ import Flutter
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
- didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index 3d43d11..dc9ada4 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 0218884..4a95797 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -17,24 +17,13 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
+ $(MARKETING_VERSION)
CFBundleSignature
????
CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
+ $(CURRENT_PROJECT_VERSION)
LSRequiresIPhoneOS
- NSAppTransportSecurity
-
- NSExceptionDomains
-
- cfr-scrapper.herokuapp.com
-
- NSExceptionAllowsInsecureHTTPLoads
-
-
-
-
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -42,6 +31,8 @@
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
@@ -52,7 +43,5 @@
UIViewControllerBasedStatusBarAppearance
- io.flutter.embedded_views_preview
-
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
index 7335fdf..308a2a5 100644
--- a/ios/Runner/Runner-Bridging-Header.h
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -1 +1 @@
-#import "GeneratedPluginRegistrant.h"
\ No newline at end of file
+#import "GeneratedPluginRegistrant.h"
diff --git a/lib/api/train_data.dart b/lib/api/train_data.dart
new file mode 100644
index 0000000..1ac8ece
--- /dev/null
+++ b/lib/api/train_data.dart
@@ -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 getTrain(int trainNumber) async {
+ final response = await http.get(Uri.https(AUTHORITY, 'train/$trainNumber'));
+ return trainDataFromJson(response.body);
+}
\ No newline at end of file
diff --git a/lib/components/cupertino_divider.dart b/lib/components/cupertino_divider.dart
new file mode 100644
index 0000000..2012a69
--- /dev/null
+++ b/lib/components/cupertino_divider.dart
@@ -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: [
+ 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: [
+ Container(
+ width: 1,
+ ),
+ Container(
+ width: 1,
+ decoration: BoxDecoration(
+ color: color,
+ ),
+ ),
+ Container(
+ width: 1,
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/components/future_display.dart b/lib/components/future_display.dart
new file mode 100644
index 0000000..075c117
--- /dev/null
+++ b/lib/components/future_display.dart
@@ -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 extends StatelessWidget {
+ final UiDesign? uiDesign;
+ final Future future;
+ final Widget Function(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,
+ );
+ },
+ );
+ }
+}
diff --git a/lib/components/loading/loading.dart b/lib/components/loading/loading.dart
new file mode 100644
index 0000000..65c8670
--- /dev/null
+++ b/lib/components/loading/loading.dart
@@ -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});
+}
\ No newline at end of file
diff --git a/lib/components/loading/loading_cupertino.dart b/lib/components/loading/loading_cupertino.dart
new file mode 100644
index 0000000..8faa1b0
--- /dev/null
+++ b/lib/components/loading/loading_cupertino.dart
@@ -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),
+ ),
+ ],
+ ),
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/lib/components/loading/loading_material.dart b/lib/components/loading/loading_material.dart
new file mode 100644
index 0000000..6fccd10
--- /dev/null
+++ b/lib/components/loading/loading_material.dart
@@ -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),
+ ),
+ ],
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/components/refresh_future_builder.dart b/lib/components/refresh_future_builder.dart
new file mode 100644
index 0000000..f107ea0
--- /dev/null
+++ b/lib/components/refresh_future_builder.dart
@@ -0,0 +1,117 @@
+import 'package:flutter/widgets.dart';
+
+class RefreshFutureBuilder extends StatefulWidget {
+ final Future Function()? futureCreator;
+ final T? initialData;
+ final Widget Function(BuildContext context, Future Function() refresh, RefreshFutureBuilderSnapshot snapshot) builder;
+
+ const RefreshFutureBuilder({ Key? key, this.futureCreator, this.initialData, required this.builder }) : super(key: key);
+
+ @override
+ _RefreshFutureBuilderState createState() => _RefreshFutureBuilderState();
+}
+
+class _RefreshFutureBuilderState extends State> {
+ late RefreshFutureBuilderSnapshot snapshot;
+ Future 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 {
+ 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,
+}
diff --git a/lib/components/select_train_suggestions/select_train_suggestions.dart b/lib/components/select_train_suggestions/select_train_suggestions.dart
new file mode 100644
index 0000000..a88ab0b
--- /dev/null
+++ b/lib/components/select_train_suggestions/select_train_suggestions.dart
@@ -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 {
+ late String userInput;
+
+ List 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: [
+ ...slivers,
+ SliverToBoxAdapter(
+ child: int.tryParse(userInput) != null ? getUseCurrentInputWidget(int.parse(userInput), widget.onTrainSelected) : Container(),
+ ),
+ SliverToBoxAdapter(
+ child: Container(
+ height: MediaQuery.of(context).viewPadding.bottom,
+ ),
+ ),
+ ],
+ );
+ }
+
+ List 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 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: [
+ mapTrainToItem(TrainOperatorTrainDescription()),
+ ],
+ ),
+ delegate: SliverChildBuilderDelegate(
+ (context, index) {
+ return Column(
+ children: [
+ 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);
+}
diff --git a/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart b/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart
new file mode 100644
index 0000000..d35d8b4
--- /dev/null
+++ b/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart
@@ -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: [
+ GestureDetector(
+ onTap: () {
+ onTrainSelected(currentInput);
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(8),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ 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: [
+ 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,
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/components/select_train_suggestions/select_train_suggestions_material.dart b/lib/components/select_train_suggestions/select_train_suggestions_material.dart
new file mode 100644
index 0000000..b8526b4
--- /dev/null
+++ b/lib/components/select_train_suggestions/select_train_suggestions_material.dart
@@ -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: [
+ 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);
+ },
+ );
+ }
+}
diff --git a/lib/components/slim_app_bar.dart b/lib/components/slim_app_bar.dart
new file mode 100644
index 0000000..7e74411
--- /dev/null
+++ b/lib/components/slim_app_bar.dart
@@ -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: [
+ 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,
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/hidden_webview.dart b/lib/hidden_webview.dart
deleted file mode 100644
index 236787d..0000000
--- a/lib/hidden_webview.dart
+++ /dev/null
@@ -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: [
- Offstage(
- offstage: true,
- child: webView,
- ),
- Positioned.fill(child: child)
- ],
- );
- }
-}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index 9bfcabe..89e53dc 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -2,195 +2,73 @@ import 'dart:io' show Platform;
import 'package:flutter/cupertino.dart';
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_cupertino.dart';
-import 'package:info_tren/train_info_page/train_info_material.dart';
-import 'package:info_tren/train_info_page/train_info_prompt.dart';
+// import 'package:flutter_redux/flutter_redux.dart';
+import 'package:info_tren/models/ui_design.dart';
+import 'package:info_tren/pages/main/main_page.dart';
+import 'package:info_tren/pages/train_info_page/view_train/train_info.dart';
+import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
-void main() => runApp(StartPoint());
+void main() {
+ // final store = createStore();
+ // runApp(
+ // StoreProvider(
+ // store: store,
+ // child: StartPoint(),
+ // )
+ // );
+ runApp(
+ StartPoint(),
+ );
+}
+
+Map routesByUiDesign(UiDesign uiDesign) => {
+ Navigator.defaultRouteName: (context) {
+ return MainPage(uiDesign: uiDesign,);
+ },
+ SelectTrainPage.routeName: (context) {
+ return SelectTrainPage(uiDesign: uiDesign);
+ },
+ TrainInfo.routeName: (context) {
+ return TrainInfo(
+ trainNumber: ModalRoute.of(context)!.settings.arguments as int,
+ );
+ },
+};
class StartPoint extends StatelessWidget {
+ final String appTitle = 'Info Tren';
+
@override
Widget build(BuildContext context) {
- if (Platform.isAndroid) {
+ if (Platform.isIOS) {
+ return CupertinoApp(
+ title: appTitle,
+ theme: CupertinoThemeData(
+ primaryColor: Colors.blue.shade600,
+ brightness: Brightness.dark,
+ // textTheme: CupertinoTextThemeData(
+ // textStyle: TextStyle(
+ // fontFamily: 'Atkinson Hyperlegible',
+ // ),
+ // ),
+ ),
+ routes: routesByUiDesign(UiDesign.CUPERTINO),
+ );
+ }
+ else {
return MaterialApp(
- title: 'Info Tren',
+ title: appTitle,
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
primaryColor: Colors.blue.shade600,
accentColor: Colors.blue.shade700,
+ // fontFamily: 'Atkinson Hyperlegible',
),
-// home: MainPageMaterial(),
- routes: {
- Navigator.defaultRouteName: (context) {
- return MainPageMaterial();
- },
- TrainInfoPromptCommon.routeName: (context) {
- return TrainInfoPromptMaterial();
- },
- TrainInfo.routeName: (context) {
- return TrainDataWebViewAdapter(
- builder: (context) {
- return TrainInfoMaterial(
- trainNumber: ModalRoute.of(context).settings.arguments as int,
- );
- },
- );
- },
- },
+ routes: routesByUiDesign(UiDesign.MATERIAL),
);
}
- else if (Platform.isIOS) {
- return CupertinoApp(
- title: "Info Tren",
- theme: CupertinoThemeData(
- primaryColor: Colors.blue.shade600,
- brightness: Brightness.dark,
- ),
-// home: MainPageCupertino(),
- routes: {
- Navigator.defaultRouteName: (context) {
- return MainPageCupertino();
- },
- TrainInfoPromptCommon.routeName: (context) {
- return TrainInfoPromptCupertino();
- },
- TrainInfo.routeName: (context) {
- return TrainDataWebViewAdapter(
- builder: (context) {
- return TrainInfoCupertino(
- trainNumber: ModalRoute.of(context).settings.arguments as int,
- );
- },
- );
- },
- }
- );
- }
- return null;
}
}
-
-mixin MainPageAction {
- onTrainInfoPageInvoke(BuildContext context) {
- Navigator.of(context).pushNamed(TrainInfoPromptCommon.routeName);
- }
-
- onStationBoardPageInvoke(BuildContext context) {
-
- }
-
- onRoutePlanPageInvoke(BuildContext context) {
-
- }
-}
-
-class MainPageMaterial extends StatelessWidget with MainPageAction {
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text("Info Tren"),
- centerTitle: true,
- ),
- body: SafeArea(
- child: Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ElevatedButton(
- child: Text(
- "Informații despre tren",
- style: Theme.of(context).textTheme.button.copyWith(fontSize: 18),
- ),
- onPressed: () {
- onTrainInfoPageInvoke(context);
- },
- ),
- ElevatedButton(
- child: Text(
- "Tabelă plecari/sosiri",
- style: Theme.of(context).textTheme.button.copyWith(fontSize: 18),
- ),
- // TODO: Implement departure/arrival
- onPressed: null,
- // onPressed: () {
- // onStationBoardPageInvoke(context);
- // },
- ),
- ElevatedButton(
- child: Text(
- "Planificare rută",
- style: Theme.of(context).textTheme.button.copyWith(fontSize: 18),
- ),
- // TODO: Implement route planning
- onPressed: null,
- // onPressed: () {
- // onRoutePlanPageInvoke(context);
- // },
- )
- ].map((w) => Padding(
- padding: const EdgeInsets.fromLTRB(4, 2, 4, 2),
- child: SizedBox(
- width: double.infinity,
- child: w,
- ),
- )).toList(),
- ),
- ),
- ),
- );
- }
-}
-
-class MainPageCupertino extends StatelessWidget with MainPageAction {
- @override
- Widget build(BuildContext context) {
- return CupertinoPageScaffold(
- navigationBar: CupertinoNavigationBar(
- middle: Text("Info Tren"),
- ),
- child: SafeArea(
- child: Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- CupertinoButton.filled(
- child: Text("Informații despre tren"),
- onPressed: () {
- onTrainInfoPageInvoke(context);
- },
- ),
- CupertinoButton.filled(
- child: Text("Tabelă plecari/sosiri"),
- // TODO: Implement departure/arrival
- onPressed: null,
- // onPressed: () {
- // onStationBoardPageInvoke(context);
- // },
- ),
- CupertinoButton.filled(
- child: Text("Planificare rută"),
- // TODO: Implement route planning
- onPressed: null,
- // onPressed: () {
- // onRoutePlanPageInvoke(context);
- // },
- ),
- ].map((w) => Padding(
- padding: const EdgeInsets.fromLTRB(4, 2, 4, 2),
- child: SizedBox(
- width: double.infinity,
- child: w,
- ),
- )).toList(),
- ),
- ),
- ),
- );
- }
-}
\ No newline at end of file
diff --git a/lib/models/train_data.dart b/lib/models/train_data.dart
index 8bc9ac9..7469334 100644
--- a/lib/models/train_data.dart
+++ b/lib/models/train_data.dart
@@ -1,1077 +1,229 @@
-import 'dart:async';
+// To parse this JSON data, do
+//
+// final trainData = trainDataFromJson(jsonString);
+
import 'dart:convert';
-import 'package:flutter/widgets.dart';
-import 'package:info_tren/hidden_webview.dart';
-import 'package:info_tren/utils/webview_invoke.dart';
-import 'package:json_annotation/json_annotation.dart';
-import 'package:webview_flutter/webview_flutter.dart';
+TrainData trainDataFromJson(String str) => TrainData.fromJson(json.decode(str));
-part 'train_data.g.dart';
+String trainDataToJson(TrainData data) => json.encode(data.toJson());
-enum TrainLookupResult {
- FOUND,
- NOT_FOUND,
- OTHER
-}
-
-class OnDemandInvalidatedException implements Exception {
- final String propertyName;
- final OnDemand onDemandClass;
-
- OnDemandInvalidatedException({this.propertyName, this.onDemandClass});
-
- @override
- String toString() {
- return "OnDemandInvalidatedException: An attempt to get $propertyName from ${onDemandClass.runtimeType} failed because the source was invalidated.";
- }
-}
-
-class OnDemand {
- bool valid;
-
- final Function onInvalidation;
-
- void invalidate() {
- if (valid) {
- valid = false;
- if (onInvalidation != null) onInvalidation();
- }
- }
-
- OnDemand(this.onInvalidation): valid = true;
-}
-
-class OnDemandTrainData extends OnDemand {
- final WebViewController _controller;
-
- OnDemandTrainData({
- WebViewController controller,
- Function onInvalidation
- })
- : _controller = controller,
- _route = OnDemandTrainRoute(controller: controller),
- _lastInfo = OnDemandLastInfo(controller: controller),
- _destination = OnDemandDestination(controller: controller),
- _nextStop = OnDemandNextStop(controller: controller),
- _stations = OnDemandStations(controller: controller),
- super(onInvalidation);
-
- @override
- invalidate() {
- super.invalidate();
- route.invalidate();
- lastInfo.invalidate();
- destination.invalidate();
- nextStop.invalidate();
- stations.invalidate();
- }
-
- Future get _originalDepartureDate async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDepartureDate");
-
- final tempRes = await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let field = table.querySelector("caption");
- return field.textContent.trim();
- })()
- """,
- isFunctionAlready: true,
- );
-
- return tempRes.split(" ").last;
- }
-
- Future get departureDate async {
- final str = await _originalDepartureDate;
-
- final parts = str.split(".").map((str) => int.parse(str)).toList();
-
- return DateTime(parts[2], parts[1], parts[0]);
- }
-
- Future get rang async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "rang");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[0];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get trainNumber async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "trainNumber");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[1];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get operator async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "operator");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[2];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- final OnDemandTrainRoute _route;
- OnDemandTrainRoute get route => _route;
-
- Future get state async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "state");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[4];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- final OnDemandLastInfo _lastInfo;
- OnDemandLastInfo get lastInfo => _lastInfo;
-
- final OnDemandDestination _destination;
- OnDemandDestination get destination => _destination;
-
- final OnDemandNextStop _nextStop;
- OnDemandNextStop get nextStop => _nextStop;
-
- Future get routeDistance async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "routeDistance");
-
- final result = (await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[12];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- )).trim();
-
- return takeWhile(result, (char) => char != ' '.codeUnitAt(0));
- }
-
- Future get _routeDuration async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_routeDuration");
-
- var result = (await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[13];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- )).trim();
-
- if (result[result.length - 1] == '.') result = result.substring(0, result.length - 1);
-
- return result;
- }
-
- Future get routeDuration async {
- final input = await _routeDuration;
-
- var result = Duration();
-
- StringBuffer buffer = StringBuffer();
-
- for (var i = 0; i < input.length; i++) {
- if ('0'.codeUnitAt(0) <= input.codeUnitAt(i) && input.codeUnitAt(i) <= '9'.codeUnitAt(0)) {
- buffer.writeCharCode(input.codeUnitAt(i));
- }
- else if (input.startsWith("min", i)) {
- result += Duration(minutes: int.parse(buffer.toString()));
- buffer = StringBuffer();
- i += 2;
- }
- else if (input.startsWith("h", i)) {
- result += Duration(hours: int.parse(buffer.toString()));
- buffer = StringBuffer();
- }
- else throw FormatException("Unrecognised!");
- }
-
- return result;
- }
-
- final OnDemandStations _stations;
- OnDemandStations get stations => _stations;
-}
-
-class OnDemandTrainRoute extends OnDemand {
- final WebViewController _controller;
-
- OnDemandTrainRoute({
- WebViewController controller,
- Function onInvalidation
- }) : _controller = controller, super(onInvalidation);
-
- Future get original async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "original");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[3];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get from async {
- final original = await this.original;
- return original.split("-")[0];
- }
-
- Future get to async {
- final original = await this.original;
- return original.split("-")[1];
- }
-}
-
-class OnDemandLastInfo extends OnDemand {
- final WebViewController _controller;
-
- OnDemandLastInfo({
- WebViewController controller,
- Function onInvalidation
- }) : _controller = controller, super(onInvalidation);
-
- Future get _lastInfoOriginal async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_lastInfoOriginal");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[5];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get station async {
- final original = await _lastInfoOriginal;
-
- return original
- .split("[")[0]
- .trim();
- }
-
- Future get event async {
- final original = await _lastInfoOriginal;
-
- return original
- .split("[")[1]
- .split("]")[0]
- .trim();
- }
-
- Future get originalDateAndTime async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "originalDateAndTime");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[6];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get dateAndTime async => parseCFRDateTime(await originalDateAndTime);
-
- Future get delay async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "delay");
-
- return int.parse(
- await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[7];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- )
- );
- }
-}
-
-class OnDemandDestination extends OnDemand {
- final WebViewController _controller;
-
- OnDemandDestination({
- WebViewController controller,
- Function onInvalidation
- }) : _controller = controller, super(onInvalidation);
-
- Future get stationName async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "destinationStation");
-
- final result = (await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[8];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- )).trim();
-
- if (result.isEmpty) return null;
- else return result;
- }
-
- Future get _originalDestinationArrival async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDestinationArrival");
-
- final result = (await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[9];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- )).trim();
-
- if (result.isEmpty) return null;
- else return result;
- }
-
- Future get arrival => _originalDestinationArrival.then((value) => parseCFRDateTime(value));
-}
-
-class OnDemandNextStop extends OnDemand {
- final WebViewController _controller;
-
- OnDemandNextStop({
- WebViewController controller,
- Function onInvalidation
- }) : _controller = controller, super(onInvalidation);
-
- Future get stationName async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "destinationStation");
-
- final result = (await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[10];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- )).trim();
-
- if (result.isEmpty) return null;
- else return result;
- }
-
- Future get _originalNextStopArrival async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDestinationArrival");
-
- final result = (await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- let rows = table.querySelectorAll("tr");
- let currentRow = rows[11];
- let currentDataCell = currentRow.querySelectorAll("td")[1];
- return currentDataCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- )).trim();
-
- if (result.isEmpty) return null;
- else return result;
- }
-
- Future get arrival => _originalNextStopArrival.then((value) => parseCFRDateTime(value));
-}
-
-class OnDemandStations extends OnDemand {
- final WebViewController _controller;
- List issuedOnDemands = [];
-
- @override
- void invalidate() {
- issuedOnDemands.map((od) => od.invalidate());
- super.invalidate();
- }
-
- OnDemandStations({
- @required WebViewController controller,
- Function onInvalidation
- }) : _controller = controller, super(onInvalidation);
-
- Future get _stationsLoaded async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_stationsLoaded");
-
- final result = await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => JSON.stringify(document.querySelector("#GridView1") == null))()
- """,
- isFunctionAlready: true,
- );
-
- final decoder = JsonDecoder();
- return !(decoder.convert(result) as bool);
- }
-
- Stream call({@required Future pageLoadFuture}) async* {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "call");
-
- if (!await _stationsLoaded) {
- await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const button = document.querySelector("#Button2");
- button.click();
- })()
- """,
- isFunctionAlready: true,
- );
- await pageLoadFuture;
- }
-
- final count = int.parse(await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const count = rowsArray.length - 1;
- return String(count);
- })()
- """,
- isFunctionAlready: true,
- ));
-
- for (int i = 1; i <= count; i++) {
- final ods = OnDemandStation(
- controller: _controller,
- index: i,
- );
- issuedOnDemands.add(ods);
- yield ods;
- }
- }
-}
-
-class OnDemandStation extends OnDemand {
- final WebViewController _controller;
- final int index;
-
- OnDemandStation({
- @required WebViewController controller,
- @required this.index,
- Function onInvalidation
- }) : _controller = controller, super(onInvalidation);
-
- Future get km async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "km");
-
- return int.parse(await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const row = rowsArray[$index];
- const columns = row.querySelectorAll("td");
- const kmCell = columns[0];
- return kmCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- ));
- }
-
- Future get stationName async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "stationName");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const row = rowsArray[$index];
- const columns = row.querySelectorAll("td");
- const kmCell = columns[1];
- return kmCell.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get arrivalTime async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "arrivalTime");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const row = rowsArray[$index];
- const columns = row.querySelectorAll("td");
- const kmCell = columns[2];
- return kmCell.textContent.trim();
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get stopsFor async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "stopsFor");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const row = rowsArray[$index];
- const columns = row.querySelectorAll("td");
- const kmCell = columns[3];
- return kmCell.textContent.trim();
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get departureTime async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "departureTime");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const row = rowsArray[$index];
- const columns = row.querySelectorAll("td");
- const kmCell = columns[4];
- return kmCell.textContent.trim();
- })()
- """,
- isFunctionAlready: true,
- );
- }
-
- Future get realOrEstimate async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "realOrEstimate");
-
- final value = await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const row = rowsArray[$index];
- const columns = row.querySelectorAll("td");
- const kmCell = columns[5];
- return kmCell.textContent.trim();
- })()
- """,
- isFunctionAlready: true,
- );
-
- if (value == "Real") return RealOrEstimate.real;
- else if (value == "Estimat") return RealOrEstimate.estimate;
- else return RealOrEstimate.UNKNOWN;
- }
-
- Future get delay async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "delay");
-
- final value = await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const row = rowsArray[$index];
- const columns = row.querySelectorAll("td");
- const kmCell = columns[6];
- return kmCell.textContent.trim();
- })()
- """,
- isFunctionAlready: true,
- );
-
- if (value.isEmpty) return 0;
- else return int.parse(value);
- }
-
- Future get observations async {
- if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "observations");
-
- return await wInvoke(
- webViewController: _controller,
- jsFunctionContent: """
- (() => {
- const table = document.querySelector("#GridView1");
- const rows = table.querySelectorAll("tr");
- const rowsArray = Array.from(rows);
- const row = rowsArray[$index];
- const columns = row.querySelectorAll("td");
- const kmCell = columns[7];
- return kmCell.textContent.trim();
- })()
- """,
- isFunctionAlready: true,
- );
- }
-}
-
-enum RealOrEstimate {
- real,
- estimate,
- UNKNOWN
-}
-
-class TrainDataWebViewAdapter extends StatefulWidget {
- final WidgetBuilder builder;
-
- TrainDataWebViewAdapter({@required this.builder});
-
- @override
- State createState() {
- return _TrainDataWebViewAdapterState();
- }
-
- static _TrainDataWebViewAdapterState of(BuildContext context) =>
- (context.findAncestorWidgetOfExactType<_TrainDataWebViewAdapterInheritedWidget>())
- .state;
-}
-
-class ProgressReport {
- final int current;
- final int total;
- final String description;
-
- ProgressReport({@required this.current, @required this.total, this.description});
-
- @override
- String toString() {
- return description == null ? "ProgressReport($current/$total)" : "ProgressReport($current/$total: $description)";
- }
-}
-
-class _TrainDataWebViewAdapterState extends State {
- Completer _webViewControllerCompleter = Completer();
- Future get webViewController => _webViewControllerCompleter.future;
-
- StreamController _pageLoadController;
- Stream pageLoadStream;
- Future get nextLoadFuture => pageLoadStream.take(1).first;
-
- StreamController _progressController;
- Stream progressStream;
-
- Future loadTrain(int trainNo) async {
- currentDatas.removeWhere((ondemand) {
- ondemand.invalidate();
- return true;
- });
-
- final controller = await webViewController;
- var nlf;
-
- nlf = nextLoadFuture;
- await controller.loadUrl("https://appiris.infofer.ro/MytrainRO.aspx");
- await nlf;
- _reportStatus(
- current: 2,
- description: "Loaded Informatica Feroviară webpage"
- );
-
- nlf = nextLoadFuture;
- await controller.evaluateJavascript("""
- ( () => {
- let inputField = document.querySelector("#TextTrnNo");
- inputField.value = $trainNo;
- let submitButton = document.querySelector("#Button1");
- submitButton.click();
- } ) ()
- """);
- await nlf;
-
- _reportStatus(
- current: 3,
- description: "Loaded train information"
- );
-
- var result = await wInvoke(
- webViewController: controller,
- jsFunctionContent: """
- (() => {
- let errorMessage = document.querySelector("#Lblx");
- return errorMessage.textContent;
- })()
- """,
- isFunctionAlready: true,
- );
-
- if (result.isNotEmpty) {
- return TrainLookupResult.NOT_FOUND;
- }
-
- final jsonDecoder = JsonDecoder();
-
- final foundTable = jsonDecoder.convert(await wInvoke(
- webViewController: controller,
- jsFunctionContent: """
- (() => {
- let table = document.querySelector("#DetailsView1");
- return JSON.stringify(table !== null);
- })()
- """,
- isFunctionAlready: true,
- )) as bool;
-
- if (foundTable) {
- return TrainLookupResult.FOUND;
- }
-
- /// Should not happen, report error in this case
- return TrainLookupResult.OTHER;
- }
-
- List currentDatas = [];
-
- Future trainData({Function onInvalidation}) async {
- final controller = await webViewController;
-
- final result = OnDemandTrainData(
- controller: controller,
- onInvalidation: onInvalidation
- );
-
- currentDatas.add(result);
-
- return result;
- }
-
- int lastStatusReported;
-
- _reportStatus({@required int current, String description}) {
- lastStatusReported = current;
- _progressController.add(ProgressReport(
- current: current,
- total: TOTAL_PROGRESS,
- description: description
- ));
- }
-
- recallLastReport() {
- _progressController.add(ProgressReport(current: lastStatusReported, total: TOTAL_PROGRESS));
- }
-
- restartProgressReport() {
- lastStatusReported = 0;
- webViewController.then((_) {
- _reportStatus(current: 1, description: "WebView created");
- });
- }
-
- @override
- void initState() {
- _pageLoadController = StreamController();
- pageLoadStream = _pageLoadController.stream.asBroadcastStream();
-
- _progressController = StreamController();
- progressStream = _progressController.stream.asBroadcastStream();
-
- lastStatusReported = 0;
-
- super.initState();
- }
-
- @override
- void dispose() {
- _pageLoadController.close();
- _progressController.close();
-
- super.dispose();
- }
-
- static const int TOTAL_PROGRESS = 3;
-
- @override
- Widget build(BuildContext context) {
- return HiddenWebView(
- webView: WebView(
- javascriptMode: JavascriptMode.unrestricted,
- onWebViewCreated: (controller) {
- _webViewControllerCompleter.complete(controller);
- _reportStatus(
- current: 1,
- description: "WebView created"
- );
- },
- onPageFinished: (url) {
- _pageLoadController.add(url);
- },
- ),
- child: _TrainDataWebViewAdapterInheritedWidget(
- child: Builder(
- builder: widget.builder,
- ),
- state: this,
- ),
- );
- }
-}
-
-class _TrainDataWebViewAdapterInheritedWidget extends InheritedWidget {
- final _TrainDataWebViewAdapterState state;
-
- _TrainDataWebViewAdapterInheritedWidget({this.state, Widget child, Key key})
- :super(key: key, child: child);
-
- @override
- bool updateShouldNotify(InheritedWidget oldWidget) {
- return true;
- }
-}
-
-@JsonSerializable()
+/// Results of scrapping InfoFer website for train info
class TrainData {
- final String rang;
- @JsonKey(name: "tren")
- final String trainNumber;
+ TrainData({
+ required this.date,
+ required this.number,
+ required this.operator,
+ required this.rank,
+ required this.route,
+ required this.stations,
+ this.status,
+ });
+
+ final String date;
+ final String number;
final String operator;
- @JsonKey(name: "relatia")
- final String route;
- @JsonKey(name: "stare")
- final String state;
- @JsonKey(name: "ultima_informatie")
- final LastInfo lastInfo;
- @JsonKey(name: "destinatie")
- final StopInfo destination;
- @JsonKey(name: "urmatoarea_oprire")
- final StopInfo nextStop;
- @JsonKey(name: "durata_calatoriei")
- final String tripLength;
- @JsonKey(name: "distanta")
- final String distance;
+ final String rank;
+ final Route route;
+ final List stations;
+ final TrainDataStatus? status;
- @JsonKey(name: "stations")
- List stations;
+ factory TrainData.fromJson(Map json) => TrainData(
+ date: json["date"],
+ number: json["number"],
+ operator: json["operator"],
+ rank: json["rank"],
+ route: Route.fromJson(json["route"]),
+ stations: List.from(
+ json["stations"].map((x) => Station.fromJson(x))),
+ status: json["status"] == null
+ ? null
+ : TrainDataStatus.fromJson(json["status"]),
+ );
- TrainData({this.rang, this.trainNumber, this.operator, this.lastInfo,
- this.state, this.route, this.tripLength, this.stations, this.nextStop,
- this.distance, this.destination});
- factory TrainData.fromJson(Map json) {
- var result = _$TrainDataFromJson(json);
- var foundEstimat = false;
- result.stations = result.stations.map((station) {
- if (station.realOrEstimate == "Estimat") {
- foundEstimat = true;
- }
+ Map toJson() => {
+ "date": date,
+ "number": number,
+ "operator": operator,
+ "rank": rank,
+ "route": route.toJson(),
+ "stations": List.from(stations.map((x) => x.toJson())),
+ "status": status?.toJson(),
+ };
+}
- station.realOrEstimate = foundEstimat ? "Estimat" : "Real";
+/// Route of the train
+class Route {
+ Route({
+ required this.from,
+ required this.to,
+ });
- return station;
- }).toList();
+ final String from;
+ final String to;
+
+ factory Route.fromJson(Map json) => Route(
+ from: json["from"],
+ to: json["to"],
+ );
+
+ Map toJson() => {
+ "from": from,
+ "to": to,
+ };
+}
+
+class Station {
+ Station({
+ this.arrival,
+ this.departure,
+ required this.km,
+ required this.name,
+ this.platform,
+ this.stoppingTime,
+ });
+
+ final StationArrDepTime? arrival;
+ final StationArrDepTime? departure;
+ final int km;
+ final String name;
+ final String? platform;
+ final int? stoppingTime;
+
+ factory Station.fromJson(Map json) => Station(
+ arrival: json["arrival"] == null
+ ? null
+ : StationArrDepTime.fromJson(json["arrival"]),
+ departure: json["departure"] == null
+ ? null
+ : StationArrDepTime.fromJson(json["departure"]),
+ km: json["km"],
+ name: json["name"],
+ platform: json["platform"] == null ? null : json["platform"],
+ stoppingTime:
+ json["stoppingTime"] == null ? null : json["stoppingTime"],
+ );
+
+ Map toJson() => {
+ "arrival": arrival?.toJson(),
+ "departure": departure?.toJson(),
+ "km": km,
+ "name": name,
+ "platform": platform == null ? null : platform,
+ "stoppingTime": stoppingTime == null ? null : stoppingTime,
+ };
+}
+
+class StationArrDepTime {
+ StationArrDepTime({
+ required this.scheduleTime,
+ this.status,
+ });
+
+ final String scheduleTime;
+ final StationArrDepTimeStatus? status;
+
+ factory StationArrDepTime.fromJson(Map json) =>
+ StationArrDepTime(
+ scheduleTime: json["scheduleTime"],
+ status: json["status"] == null ? null : StationArrDepTimeStatus.fromJson(json["status"]),
+ );
+
+ Map toJson() => {
+ "scheduleTime": scheduleTime,
+ "status": status?.toJson(),
+ };
+}
+
+class StationArrDepTimeStatus {
+ StationArrDepTimeStatus({
+ required this.delay,
+ required this.real,
+ });
+
+ final int delay;
+ final bool real;
+
+ factory StationArrDepTimeStatus.fromJson(Map json) =>
+ StationArrDepTimeStatus(
+ delay: json["delay"],
+ real: json["real"],
+ );
+
+ Map toJson() => {
+ "delay": delay,
+ "real": real,
+ };
+}
+
+class TrainDataStatus {
+ TrainDataStatus({
+ required this.delay,
+ required this.state,
+ required this.station,
+ });
+
+ final int delay;
+ final State state;
+ final String station;
+
+ factory TrainDataStatus.fromJson(Map json) =>
+ TrainDataStatus(
+ delay: json["delay"],
+ state: stateValues.map[json["state"]]!,
+ station: json["station"],
+ );
+
+ Map toJson() => {
+ "delay": delay,
+ "state": stateValues.reverse[state],
+ "station": station,
+ };
+
+ @override
+ String toString() {
+ String result = '';
+ if (delay == 0) {
+ result += 'La timp';
+ }
+ else {
+ result += '${delay.abs()} min';
+ }
+ result += ' la ';
+ switch (state) {
+ case State.PASSING:
+ result += 'trecerea fără oprire prin';
+ break;
+ case State.ARRIVAL:
+ result += 'sosirea în';
+ break;
+ case State.DEPARTURE:
+ result += 'plecarea din';
+ break;
+ }
+ result += station;
return result;
}
- Map toJson() => _$TrainDataToJson(this);
}
-@JsonSerializable()
-class LastInfo {
- @JsonKey(name: "statia")
- final String station;
- @JsonKey(name: "eveniment")
- final String event;
- @JsonKey(name: "data_si_ora")
- final String dateAndTime;
- DateTime get formattedDateAndTime {
- return parseCFRDateTime(dateAndTime);
+enum State { PASSING, ARRIVAL, DEPARTURE }
+
+final stateValues = EnumValues({
+ "arrival": State.ARRIVAL,
+ "departure": State.DEPARTURE,
+ "passing": State.PASSING
+});
+
+class EnumValues {
+ Map map;
+ Map? reverseMap;
+
+ EnumValues(this.map);
+
+ Map get reverse {
+ if (reverseMap == null) {
+ reverseMap = map.map((k, v) => new MapEntry(v, k));
+ }
+ return reverseMap!;
}
- @JsonKey(name: "intarziere")
- final int delay;
-
-
- LastInfo({this.dateAndTime, this.delay, this.event, this.station});
-
- factory LastInfo.fromJson(Map json) => _$LastInfoFromJson(json);
- Map toJson() => _$LastInfoToJson(this);
}
-
-@JsonSerializable()
-class StopInfo {
- @JsonKey(name: "statia")
- final String station;
- @JsonKey(name: "data_si_ora")
- final String dateAndTime;
- DateTime get formattedDateAndTime {
- return parseCFRDateTime(dateAndTime);
- }
-
- StopInfo({this.station, this.dateAndTime});
-
- factory StopInfo.fromJson(Map json) => _$StopInfoFromJson(json);
- Map toJson() => _$StopInfoToJson(this);
-}
-
-@JsonSerializable()
-class StationEntry {
- final String km;
- @JsonKey(name: "statia")
- final String name;
- @JsonKey(name: "sosire")
- final String arrivalTime;
- @JsonKey(name: "stationeaza_pentru")
- final String waitTime;
- @JsonKey(name: "plecare")
- final String departureTime;
- @JsonKey(name: "real/estimat")
- String realOrEstimate;
- bool get real {
- return realOrEstimate == "Real";
- }
- @JsonKey(name: "intarziere")
- final int delay;
- @JsonKey(name: "observatii")
- final String observations;
-
- StationEntry({this.name, this.delay, this.realOrEstimate,
- this.arrivalTime, this.departureTime, this.km, this.observations,
- this.waitTime});
-
- factory StationEntry.fromJson(Map json) => _$StationEntryFromJson(json);
- Map toJson() => _$StationEntryToJson(this);
-}
-
-DateTime parseCFRDateTime(String dateAndTime) {
- if (dateAndTime == null || dateAndTime.isEmpty) return null;
-
- final parts = dateAndTime.split(" ");
-
- final dateParts = parts[0].split(".");
- final day = int.parse(dateParts[0]);
- final month = int.parse(dateParts[1]);
- final year = int.parse(dateParts[2]);
-
- final timeParts = parts[1].split(":");
- final hour = int.parse(timeParts[0]);
- final minute = int.parse(timeParts[1]);
-
- return DateTime(year, month, day, hour, minute);
-}
-
-String takeWhile(String input, Function charValidator) {
- StringBuffer output = StringBuffer();
-
- for (final char in input.codeUnits) {
- if (charValidator(char)) output.writeCharCode(char);
- else break;
- }
-
- return output.toString();
-}
\ No newline at end of file
diff --git a/lib/models/train_data.g.dart b/lib/models/train_data.g.dart
deleted file mode 100644
index d90192d..0000000
--- a/lib/models/train_data.g.dart
+++ /dev/null
@@ -1,98 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-part of 'train_data.dart';
-
-// **************************************************************************
-// JsonSerializableGenerator
-// **************************************************************************
-
-TrainData _$TrainDataFromJson(Map 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),
- 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))
- ?.toList(),
- nextStop: json['urmatoarea_oprire'] == null
- ? null
- : StopInfo.fromJson(
- json['urmatoarea_oprire'] as Map),
- distance: json['distanta'] as String,
- destination: json['destinatie'] == null
- ? null
- : StopInfo.fromJson(json['destinatie'] as Map));
-}
-
-Map _$TrainDataToJson(TrainData instance) => {
- '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 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 _$LastInfoToJson(LastInfo instance) => {
- 'statia': instance.station,
- 'eveniment': instance.event,
- 'data_si_ora': instance.dateAndTime,
- 'intarziere': instance.delay
- };
-
-StopInfo _$StopInfoFromJson(Map json) {
- return StopInfo(
- station: json['statia'] as String,
- dateAndTime: json['data_si_ora'] as String);
-}
-
-Map _$StopInfoToJson(StopInfo instance) => {
- 'statia': instance.station,
- 'data_si_ora': instance.dateAndTime
- };
-
-StationEntry _$StationEntryFromJson(Map 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 _$StationEntryToJson(StationEntry instance) =>
- {
- '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
- };
diff --git a/lib/models/train_operator_lines.dart b/lib/models/train_operator_lines.dart
new file mode 100644
index 0000000..ed408af
--- /dev/null
+++ b/lib/models/train_operator_lines.dart
@@ -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 trains;
+
+ TrainOperatorLines({
+ required this.operator,
+ this.shortName = "",
+ required this.version,
+ required this.trains,
+ });
+
+ factory TrainOperatorLines.fromJson(Map json) => _$TrainOperatorLinesFromJson(json);
+ Map 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 json) => _$TrainOperatorTrainDescriptionFromJson(json);
+ Map toJson() => _$TrainOperatorTrainDescriptionToJson(this);
+}
diff --git a/lib/models/train_operator_lines.g.dart b/lib/models/train_operator_lines.g.dart
new file mode 100644
index 0000000..c1ab5ec
--- /dev/null
+++ b/lib/models/train_operator_lines.g.dart
@@ -0,0 +1,42 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'train_operator_lines.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+TrainOperatorLines _$TrainOperatorLinesFromJson(Map json) =>
+ TrainOperatorLines(
+ operator: json['operator'] as String,
+ shortName: json['short_name'] as String? ?? "",
+ version: json['versiune'] as String,
+ trains: (json['trenuri'] as List)
+ .map((e) =>
+ TrainOperatorTrainDescription.fromJson(e as Map))
+ .toList(),
+ );
+
+Map _$TrainOperatorLinesToJson(TrainOperatorLines instance) =>
+ {
+ 'short_name': instance.shortName,
+ 'operator': instance.operator,
+ 'versiune': instance.version,
+ 'trenuri': instance.trains,
+ };
+
+TrainOperatorTrainDescription _$TrainOperatorTrainDescriptionFromJson(
+ Map json) =>
+ TrainOperatorTrainDescription(
+ number: json['numar'] as String? ?? '',
+ rang: json['rang'] as String? ?? '',
+ internalNumber: json['numar_intern'] as int? ?? 0,
+ );
+
+Map _$TrainOperatorTrainDescriptionToJson(
+ TrainOperatorTrainDescription instance) =>
+ {
+ 'rang': instance.rang,
+ 'numar': instance.number,
+ 'numar_intern': instance.internalNumber,
+ };
diff --git a/lib/models/ui_design.dart b/lib/models/ui_design.dart
new file mode 100644
index 0000000..19ebc75
--- /dev/null
+++ b/lib/models/ui_design.dart
@@ -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';
+ }
+}
diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart
new file mode 100644
index 0000000..6734556
--- /dev/null
+++ b/lib/pages/main/main_page.dart
@@ -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 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});
+}
diff --git a/lib/pages/main/main_page_cupertino.dart b/lib/pages/main/main_page_cupertino.dart
new file mode 100644
index 0000000..4e0d2df
--- /dev/null
+++ b/lib/pages/main/main_page_cupertino.dart
@@ -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(),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/main/main_page_material.dart b/lib/pages/main/main_page_material.dart
new file mode 100644
index 0000000..1a78d76
--- /dev/null
+++ b/lib/pages/main/main_page_material.dart
@@ -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(),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/train_info_page/select_train/select_train.dart b/lib/pages/train_info_page/select_train/select_train.dart
new file mode 100644
index 0000000..545c6ff
--- /dev/null
+++ b/lib/pages/train_info_page/select_train/select_train.dart
@@ -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 {
+ 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),
+ );
+}
diff --git a/lib/pages/train_info_page/select_train/select_train_cupertino.dart b/lib/pages/train_info_page/select_train/select_train_cupertino.dart
new file mode 100644
index 0000000..e9d0ba4
--- /dev/null
+++ b/lib/pages/train_info_page/select_train/select_train_cupertino.dart
@@ -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: [
+ 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,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/train_info_page/select_train/select_train_material.dart b/lib/pages/train_info_page/select_train/select_train_material.dart
new file mode 100644
index 0000000..2edc436
--- /dev/null
+++ b/lib/pages/train_info_page/select_train/select_train_material.dart
@@ -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: [
+ 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,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/train_info_page/train_info_animation_helpers.dart b/lib/pages/train_info_page/train_info_animation_helpers.dart.old
similarity index 98%
rename from lib/train_info_page/train_info_animation_helpers.dart
rename to lib/pages/train_info_page/train_info_animation_helpers.dart.old
index dc891a7..7a34ff7 100644
--- a/lib/train_info_page/train_info_animation_helpers.dart
+++ b/lib/pages/train_info_page/train_info_animation_helpers.dart.old
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
-import 'package:info_tren/train_info_page/train_info_constants.dart';
+import 'package:info_tren/pages/train_info_page/train_info_constants.dart';
import 'dart:io' show Platform;
diff --git a/lib/train_info_page/train_info_constants.dart b/lib/pages/train_info_page/train_info_constants.dart
similarity index 100%
rename from lib/train_info_page/train_info_constants.dart
rename to lib/pages/train_info_page/train_info_constants.dart
diff --git a/lib/pages/train_info_page/view_train/train_info.dart b/lib/pages/train_info_page/view_train/train_info.dart
new file mode 100644
index 0000000..0ad578f
--- /dev/null
+++ b/lib/pages/train_info_page/view_train/train_info.dart
@@ -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(
+ 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});
+}
diff --git a/lib/pages/train_info_page/view_train/train_info_cupertino.dart b/lib/pages/train_info_page/view_train/train_info_cupertino.dart
new file mode 100644
index 0000000..4961637
--- /dev/null
+++ b/lib/pages/train_info_page/view_train/train_info_cupertino.dart
@@ -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: [
+ 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: [
+ // 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: [
+ // // 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(
+ // 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: [
+ // 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: [
+ // 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: [
+ // 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: [
+ 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: [
+ 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: [
+ 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(
+ // 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: [
+// 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(
+// 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: [
+// 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: [
+ 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: [
+ // 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: [
+ 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: [
+// Text(
+// "Durata rutei",
+// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
+// fontSize: 18,
+// fontWeight: FontWeight.bold,
+// ),
+// textAlign: TextAlign.center,
+// ),
+// FutureDisplay(
+// 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,
+ ),
+ );
+ }
+}
diff --git a/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart
new file mode 100644
index 0000000..864cc30
--- /dev/null
+++ b/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart
@@ -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: [
+ Row(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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();
+ }
+}
diff --git a/lib/pages/train_info_page/view_train/train_info_material.dart b/lib/pages/train_info_page/view_train/train_info_material.dart
new file mode 100644
index 0000000..8b2060d
--- /dev/null
+++ b/lib/pages/train_info_page/view_train/train_info_material.dart
@@ -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: [
+ if (isSmallScreen(context))
+ SlimAppBar(
+ title: 'INFO TREN - ${trainData.rank} ${trainData.number}'
+ ),
+ Expanded(
+ child: SafeArea(
+ bottom: false,
+ child: CustomScrollView(
+ slivers: [
+ 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: [
+ // Expanded(child: DisplayTrainNextStop(trainData: trainData,)),
+ Expanded(child: DisplayTrainDestination(trainData: trainData,)),
+ Expanded(child: DisplayTrainRouteDistance(trainData: trainData,),),
+ ],
+ ),
+ ),
+ ),
+ // SliverToBoxAdapter(
+ // child: IntrinsicHeight(
+ // child: Row(
+ // children: [
+ // // 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: [
+ 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: [
+ 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: [
+ 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: [
+ // FutureDisplay(
+ // 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: [
+// 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(
+// 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: [
+// 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: [
+ 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: [
+ // 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: [
+ 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: [
+// Text(
+// "Durata rutei",
+// style: Theme.of(context).textTheme.bodyText2?.copyWith(
+// fontSize: isSmallScreen(context) ? 16 : 18,
+// fontWeight: FontWeight.bold,
+// ),
+// textAlign: TextAlign.center,
+// ),
+// FutureDisplay(
+// 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,
+ ),
+ );
+ }
+}
+
diff --git a/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart
new file mode 100644
index 0000000..01f6ac4
--- /dev/null
+++ b/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart
@@ -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: [
+ Row(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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: [
+ 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();
+ }
+}
diff --git a/lib/stations_list.dart b/lib/stations_list.dart.old
similarity index 78%
rename from lib/stations_list.dart
rename to lib/stations_list.dart.old
index a69964d..6f8129f 100644
--- a/lib/stations_list.dart
+++ b/lib/stations_list.dart.old
@@ -40,7 +40,7 @@ enum NotchStyle {
}
class StopListLine extends StatelessWidget {
- final StationEntry station;
+ final Station station;
final int width;
StopListLine(this.station, {this.width = 32}) : assert(width.isEven);
@@ -151,7 +151,7 @@ class StopListLinePainter extends CustomPainter {
}
class StopOnLineDetails extends StatelessWidget {
- final StationEntry station;
+ final Station station;
StopOnLineDetails(this.station);
@override
@@ -172,11 +172,11 @@ class StopOnLineDetails extends StatelessWidget {
textAlign: TextAlign.center,
),
),
- if (station.observations == "ONI")
- Padding(
- padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.5),
- child: Text("oprire ne-itinerarică", style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700),),
- ),
+ // if (station.observations == "ONI")
+ // Padding(
+ // padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.5),
+ // child: Text("oprire ne-itinerarică", style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700),),
+ // ),
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 0.5, 8.0, 8.0),
child: Text(
@@ -186,11 +186,12 @@ class StopOnLineDetails extends StatelessWidget {
),
),
StopOnLineTimeDetails(station),
- if (station.real)
- Padding(
- padding: const EdgeInsets.all(2.0),
- child: StopOnLineDelayDetails(station),
- ),
+ // TODO: Figure out how to display delay info
+ // if (station.arrival != null && station.arrival.status != null || station.departure != null && station.departure.status != null)
+ // Padding(
+ // padding: const EdgeInsets.all(2.0),
+ // child: StopOnLineDelayDetails(station),
+ // ),
Divider(
height: 0,
),
@@ -201,14 +202,14 @@ class StopOnLineDetails extends StatelessWidget {
}
class StopOnLineTimeDetails extends StatelessWidget {
- final StationEntry station;
+ final Station station;
StopOnLineTimeDetails(this.station);
@override
Widget build(BuildContext context) {
return Row(
children: [
- if (station.arrivalTime.isNotEmpty)
+ if (station.arrival != null)
Padding(
padding: const EdgeInsets.all(4.0),
child: Align(
@@ -221,14 +222,14 @@ class StopOnLineTimeDetails extends StatelessWidget {
textAlign: TextAlign.left,
),
Text(
- station.arrivalTime,
+ station.arrival.scheduleTime,
textAlign: TextAlign.left,
)
],
),
),
),
- if (station.waitTime.isNotEmpty)
+ if (station.stoppingTime != null)
Expanded(
child: Padding(
padding: const EdgeInsets.all(4.0),
@@ -242,7 +243,7 @@ class StopOnLineTimeDetails extends StatelessWidget {
textAlign: TextAlign.center,
),
Text(
- "${station.waitTime} ${station.waitTime == "1" ? "minut" : "minute"}",
+ "${station.stoppingTime} ${station.stoppingTime == 1 ? "minut" : "minute"}",
textAlign: TextAlign.center,
)
],
@@ -252,7 +253,7 @@ class StopOnLineTimeDetails extends StatelessWidget {
)
else
Expanded(child: Container(),),
- if (station.departureTime.isNotEmpty)
+ if (station.departure != null)
Padding(
padding: const EdgeInsets.all(4.0),
child: Align(
@@ -265,7 +266,7 @@ class StopOnLineTimeDetails extends StatelessWidget {
textAlign: TextAlign.right,
),
Text(
- station.departureTime,
+ station.departure.scheduleTime,
textAlign: TextAlign.right,
)
],
@@ -277,32 +278,32 @@ class StopOnLineTimeDetails extends StatelessWidget {
}
}
-class StopOnLineDelayDetails extends StatelessWidget {
- final StationEntry station;
- StopOnLineDelayDetails(this.station);
+// class StopOnLineDelayDetails extends StatelessWidget {
+// final Station station;
+// StopOnLineDelayDetails(this.station);
- @override
- Widget build(BuildContext context) {
- if (station.delay == 0) {
- return Text(
- "Fără întârziere",
- style: Theme.of(context).textTheme.caption,
- textAlign: TextAlign.center,
- );
- }
- else if (station.delay < 0) {
- return Text(
- "${-(station.delay)} minute mai devreme",
- style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.green.shade700),
- textAlign: TextAlign.center,
- );
- }
- else {
- return Text(
- "${station.delay} minute întârziere",
- style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700),
- textAlign: TextAlign.center,
- );
- }
- }
-}
\ No newline at end of file
+// @override
+// Widget build(BuildContext context) {
+// if (station.delay == 0) {
+// return Text(
+// "Fără întârziere",
+// style: Theme.of(context).textTheme.caption,
+// textAlign: TextAlign.center,
+// );
+// }
+// else if (station.delay < 0) {
+// return Text(
+// "${-(station.delay)} minute mai devreme",
+// style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.green.shade700),
+// textAlign: TextAlign.center,
+// );
+// }
+// else {
+// return Text(
+// "${station.delay} minute întârziere",
+// style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700),
+// textAlign: TextAlign.center,
+// );
+// }
+// }
+// }
\ No newline at end of file
diff --git a/lib/train_info_display.dart b/lib/train_info_display.dart
index 6f298dc..abf88fb 100644
--- a/lib/train_info_display.dart
+++ b/lib/train_info_display.dart
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
-import 'package:info_tren/stations_list.dart';
+import 'package:info_tren/stations_list.dart.old';
import 'models/train_data.dart';
@@ -29,33 +29,30 @@ class TrainInfoDisplayData extends StatelessWidget {
padding: const EdgeInsets.all(4.0),
child: TotalDetails(trainData),
),
- if (trainData.destination.station.isNotEmpty)
- ...[
- CustomDivider(),
- Padding(
- padding: const EdgeInsets.all(4.0),
- child: Destination(trainData),
- ),
- ],
+ CustomDivider(),
+ Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: Destination(trainData),
+ ),
CustomDivider(),
Padding(
padding: const EdgeInsets.all(4.0),
child: LastUpdate(trainData),
),
- if (trainData.nextStop.station.isNotEmpty)
- ...[
- CustomDivider(),
- Padding(
- padding: const EdgeInsets.all(4.0),
- child: NextStop(trainData),
- ),
- ],
+ // if (trainData.nextStop.station.isNotEmpty)
+ // ...[
+ // CustomDivider(),
+ // Padding(
+ // padding: const EdgeInsets.all(4.0),
+ // child: NextStop(trainData),
+ // ),
+ // ],
CustomDivider(),
Padding(
padding: const EdgeInsets.all(4.0),
child: TrainStatus(trainData),
),
- Divider(color: Theme.of(context).accentColor,),
+ Divider(color: Theme.of(context).colorScheme.secondary,),
Padding(
padding: const EdgeInsets.all(4.0),
child: StationsList(trainData),
@@ -81,7 +78,7 @@ class TrainName extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
- "${trainData.rang} ${trainData.trainNumber}",
+ "${trainData.rank} ${trainData.number}",
style: Theme.of(context).textTheme.headline3,
);
}
@@ -98,20 +95,20 @@ class TrainRoute extends StatelessWidget {
children: [
Expanded(
child: Text(
- "${trainData.route.split("-")[0]}",
- style: Theme.of(context).textTheme.bodyText1.copyWith(fontStyle: FontStyle.italic),
+ trainData.route.from,
+ style: Theme.of(context).textTheme.bodyText1?.copyWith(fontStyle: FontStyle.italic),
textAlign: TextAlign.left,
),
),
Text(
"-",
- style: Theme.of(context).textTheme.bodyText1.copyWith(fontStyle: FontStyle.italic),
+ style: Theme.of(context).textTheme.bodyText1?.copyWith(fontStyle: FontStyle.italic),
textAlign: TextAlign.center,
),
Expanded(
child: Text(
- "${trainData.route.split("-")[1]}",
- style: Theme.of(context).textTheme.bodyText1.copyWith(fontStyle: FontStyle.italic),
+ trainData.route.to,
+ style: Theme.of(context).textTheme.bodyText1?.copyWith(fontStyle: FontStyle.italic),
textAlign: TextAlign.right,
),
),
@@ -141,7 +138,7 @@ class TrainStatus extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
- trainData.state,
+ trainData.status.toString(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline5,
);
@@ -154,16 +151,16 @@ class Destination extends StatelessWidget {
@override
Widget build(BuildContext context) {
- if (trainData.destination.station.isEmpty) return Container();
+ final destinationStation = trainData.stations.last;
return Column(
children: [
Text(
- "Destinația: ${trainData.destination.station}",
+ "Destinația: ${destinationStation.name}",
textAlign: TextAlign.center,
),
Text(
- "Sosește în ${trainData.destination.dateAndTime.split(" ")[0]} la ${trainData.destination.dateAndTime.split(" ")[1]}",
+ "Sosește la ${destinationStation.arrival!.scheduleTime}",
textAlign: TextAlign.center,
),
],
@@ -177,6 +174,9 @@ class LastUpdate extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ if (trainData.status == null) {
+ return Container();
+ }
return Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -188,62 +188,63 @@ class LastUpdate extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.all(2.0),
- child: Text(trainData.lastInfo.station, textAlign: TextAlign.left,),
+ child: Text(trainData.status!.station, textAlign: TextAlign.left,),
),
Expanded(child: Container(),),
Padding(
padding: const EdgeInsets.all(2.0),
- child: Text(trainData.lastInfo.event, textAlign: TextAlign.right,),
+ child: Text(trainData.status!.state.toString(), textAlign: TextAlign.right,),
)
],
),
Padding(
padding: const EdgeInsets.all(2.0),
- child: trainData.lastInfo.delay == 0
+ child: trainData.status!.delay == 0
? Text("Fără întârziere", style: Theme.of(context).textTheme.caption,)
- : trainData.lastInfo.delay > 0
- ? Text("${trainData.lastInfo.delay} minute întârziere", style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700),)
- : Text("${-(trainData.lastInfo.delay)} minute mai devreme", style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.green.shade700),)
- ),
- Padding(
- padding: const EdgeInsets.all(2.0),
- child: Text("Raportat la ${trainData.lastInfo.dateAndTime}"),
+ : trainData.status!.delay > 0
+ ? Text("${trainData.status!.delay} minute întârziere", style: Theme.of(context).textTheme.bodyText2?.copyWith(color: Colors.red.shade700),)
+ : Text("${-(trainData.status!.delay)} minute mai devreme", style: Theme.of(context).textTheme.bodyText2?.copyWith(color: Colors.green.shade700),)
),
+ // TODO: Implement status report time detection
+ // Padding(
+ // padding: const EdgeInsets.all(2.0),
+ // child: Text("Raportat la ${trainData.lastInfo.dateAndTime}"),
+ // ),
],
);
}
}
-class NextStop extends StatelessWidget {
- final TrainData trainData;
- NextStop(this.trainData);
+// class NextStop extends StatelessWidget {
+// final TrainData trainData;
+// NextStop(this.trainData);
- @override
- Widget build(BuildContext context) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding: const EdgeInsets.all(2.0),
- child: Text("Următoarea oprire", style: Theme.of(context).textTheme.headline5,),
- ),
- Row(
- children: [
- Padding(
- padding: const EdgeInsets.all(2.0),
- child: Text(trainData.nextStop.station, textAlign: TextAlign.left,),
- ),
- Expanded(child: Container(),),
- Padding(
- padding: const EdgeInsets.all(2.0),
- child: Text(trainData.nextStop.dateAndTime, textAlign: TextAlign.right,),
- )
- ],
- ),
- ],
- );
- }
-}
+// @override
+// Widget build(BuildContext context) {
+// return Column(
+// mainAxisSize: MainAxisSize.min,
+// children: [
+// Padding(
+// padding: const EdgeInsets.all(2.0),
+// child: Text("Următoarea oprire", style: Theme.of(context).textTheme.headline5,),
+// ),
+// Row(
+// children: [
+// Padding(
+// padding: const EdgeInsets.all(2.0),
+// child: Text(trainData.nextStop.station, textAlign: TextAlign.left,),
+// ),
+// Expanded(child: Container(),),
+// Padding(
+// padding: const EdgeInsets.all(2.0),
+// child: Text(trainData.nextStop.dateAndTime, textAlign: TextAlign.right,),
+// )
+// ],
+// ),
+// ],
+// );
+// }
+// }
class TotalDetails extends StatelessWidget {
final TrainData trainData;
@@ -254,18 +255,18 @@ class TotalDetails extends StatelessWidget {
return Row(
children: [
Text(
- trainData.distance,
+ '${trainData.stations.last.km} km',
style: Theme.of(context).textTheme.caption,
textAlign: TextAlign.left,
),
Expanded(
child: Container()
),
- Text(
- trainData.tripLength,
- style: Theme.of(context).textTheme.caption,
- textAlign: TextAlign.right,
- )
+ // Text(
+ // trainData.tripLength,
+ // style: Theme.of(context).textTheme.caption,
+ // textAlign: TextAlign.right,
+ // )
],
);
}
diff --git a/lib/train_info_page/train_info.dart b/lib/train_info_page/train_info.dart
deleted file mode 100644
index 7b25a21..0000000
--- a/lib/train_info_page/train_info.dart
+++ /dev/null
@@ -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(BuildContext context, T data);
-
-class FutureDisplay extends StatelessWidget {
- final Future future;
- final FutureDisplayCallback 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,
- );
- },
- );
- }
-}
\ No newline at end of file
diff --git a/lib/train_info_page/train_info_cupertino.dart b/lib/train_info_page/train_info_cupertino.dart
deleted file mode 100644
index 93aa3ec..0000000
--- a/lib/train_info_page/train_info_cupertino.dart
+++ /dev/null
@@ -1,1022 +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_animation_helpers.dart';
-import 'package:info_tren/train_info_page/train_info_constants.dart';
-import 'package:info_tren/train_info_page/train_info_cupertino_DisplayTrainStation.dart';
-import 'package:info_tren/utils/stream_list.dart';
-
-class TrainInfoCupertino extends StatefulWidget {
- final int trainNumber;
-
- TrainInfoCupertino({@required this.trainNumber});
-
- @override
- _TrainInfoCupertinoState createState() => _TrainInfoCupertinoState();
-}
-
-class _TrainInfoCupertinoState extends State 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 _TrainDataCupertinoBefore(
- title: title,
- lookupResult: lookupResult,
- );
- }
- else {
- return _TrainDataCupertinoAfter(
- title: title,
- );
- }
- }
-}
-
-class _TrainDataCupertinoBefore extends StatefulWidget {
- final String title;
- final TrainLookupResult lookupResult;
-
- _TrainDataCupertinoBefore({
- @required this.title,
- @required this.lookupResult,
- });
-
- @override
- __TrainDataCupertinoBeforeState createState() => __TrainDataCupertinoBeforeState();
-}
-
-class __TrainDataCupertinoBeforeState extends State<_TrainDataCupertinoBefore> {
- @override
- Widget build(BuildContext context) {
- return CupertinoPageScaffold(
- navigationBar: CupertinoNavigationBar(
- middle: Text(widget.title ?? ""),
- ),
- child: SafeArea(
- child: StreamBuilder(
- 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: [
- CupertinoActivityIndicator(),
- Text(
- "Conectare...",
- style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
- fontSize: 18,
- fontWeight: FontWeight.bold,
- ),
- ),
- ],
- ),
- );
- case ConnectionState.active:
- break;
- case ConnectionState.done:
- Navigator.of(context).pop();
- return Container();
- }
-
- return Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- 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: CupertinoTheme.of(context).scaffoldBackgroundColor,
- backgroundColor:
- widget.lookupResult == TrainLookupResult.FOUND
- ? BACKGROUND_GREEN
- : BACKGROUND_RED,
- child: Center(
- child: Row(
- children: [
- Expanded(child: Container(),),
- if (widget.lookupResult == TrainLookupResult.FOUND)
- Padding(
- padding: const EdgeInsets.fromLTRB(8, 8, 0, 8),
- child: Center(
- child: CupertinoActivityIndicator()
- ),
- ),
- 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: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 20),
- ),
- ),
- Expanded(child: Container(),),
- ],
- ),
- ),
- ),
- ),
- ],
- ],
- ),
- );
- }
- ),
- ),
- );
- }
-}
-
-class _TrainDataCupertinoAfter extends StatefulWidget {
- final String title;
-
- _TrainDataCupertinoAfter({
- @required this.title,
- });
-
- @override
- __TrainDataCupertinoAfterState createState() => __TrainDataCupertinoAfterState();
-}
-
-class __TrainDataCupertinoAfterState extends State<_TrainDataCupertinoAfter> {
- @override
- Widget build(BuildContext context) {
- return FutureBuilder(
- future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () {
- Navigator.of(context).pop();
- }),
- builder: (context, snapshot) {
- if (!snapshot.hasData) {
- return CupertinoPageScaffold(
- navigationBar: CupertinoNavigationBar(
- middle: Text(widget.title ?? ""),
- ),
- child: SafeArea(
- child: Center(
- child: CupertinoActivityIndicator(),
- ),
- ),
- );
- }
-
- return CupertinoPageScaffold(
- navigationBar: CupertinoNavigationBar(
- middle: FutureBuilder>(
- 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 ?? "");
- }
- },
- ),
- ),
- child: SafeArea(
- top: false,
- bottom: false,
- child: Builder(
- builder: (context) {
- final topPadding = MediaQuery.of(context).padding.top;
-
- return CustomScrollView(
- slivers: [
- SliverToBoxAdapter(
- child: Padding(
- padding: EdgeInsets.only(
- top: topPadding,
- ),
- child: Container(),
- ),
- ),
- DisplayTrainID(trainData: snapshot.data,),
- DisplayTrainOperator(trainData: snapshot.data,),
- DisplayTrainRoute(trainData: snapshot.data,),
- DisplayTrainDeparture(trainData: snapshot.data,),
- SliverToBoxAdapter(
- child: CupertinoDivider(
- color: FOREGROUND_WHITE,
- ),
- ),
- DisplayTrainLastInfo(trainData: snapshot.data,),
- SliverToBoxAdapter(
- child: CupertinoDivider(),
- ),
- SliverToBoxAdapter(
- child: IntrinsicHeight(
- child: Row(
- children: [
- Expanded(
- child: DisplayTrainNextStop(trainData: snapshot.data,),
- ),
- SizedBox(
- height: double.infinity,
- child: CupertinoVerticalDivider(),
- ),
- Expanded(
- child: DisplayTrainDestination(trainData: snapshot.data,),
- )
- ],
- ),
- ),
- ),
- SliverToBoxAdapter(
- child: CupertinoDivider(),
- ),
- SliverToBoxAdapter(
- child: IntrinsicHeight(
- child: Row(
- children: [
- Expanded(
- child: DisplayTrainRouteDuration(trainData: snapshot.data,),
- ),
- SizedBox(
- height: double.infinity,
- child: CupertinoVerticalDivider(),
- ),
- Expanded(
- child: DisplayTrainRouteDistance(trainData: snapshot.data,),
- )
- ],
- ),
- ),
- ),
- SliverToBoxAdapter(
- child: CupertinoDivider(
- color: FOREGROUND_WHITE,
- ),
- ),
- DisplayTrainStations(
- trainData: snapshot.data,
- pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture,
- ),
- SliverToBoxAdapter(
- child: Container(
- height: MediaQuery.of(context).viewPadding.bottom,
- ),
- ),
- ],
- );
- }
- ),
- ),
- );
- },
- );
-
- // return CupertinoPageScaffold(
- // navigationBar: CupertinoNavigationBar(
- // middle: Text(title ?? ""),
- // ),
- // child: SafeArea(
- // bottom: false,
- // child: FutureBuilder(
- // 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: [
- // 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: [
- // 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: [
- // 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 OnDemandTrainData trainData;
- DisplayTrainID({@required this.trainData});
-
- @override
- Widget build(BuildContext context) {
- return SliverToBoxAdapter(
- child: FutureDisplay(
- future: Future.wait([
- trainData.rang,
- trainData.trainNumber
- ]),
- builder: (context, datas) {
- return Center(
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(
- "${datas[0]} ${datas[1]}",
- style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
- ),
- ),
- );
- },
- ),
- );
- }
-}
-
-class DisplayTrainRoute extends StatelessWidget {
- final OnDemandTrainData trainData;
-
- DisplayTrainRoute({@required this.trainData});
-
- @override
- Widget build(BuildContext context) {
- return SliverToBoxAdapter(
- child: FutureDisplay(
- future: Future.wait([trainData.route.from, trainData.route.to]),
- builder: (context, routePieces) {
- return Row(
- children: [
- Center(
- child: Padding(
- padding: const EdgeInsets.all(4),
- child: Text(
- routePieces[0],
- 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(
- routePieces[1],
- style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
- fontSize: 16,
- ),
- textAlign: TextAlign.right,
- ),
- ),
- ),
- ],
- );
- },
- ),
- );
- }
-}
-
-class DisplayTrainOperator extends StatelessWidget {
- final OnDemandTrainData trainData;
-
- DisplayTrainOperator({@required this.trainData});
-
- @override
- Widget build(BuildContext context) {
- return SliverToBoxAdapter(
- child: FutureDisplay(
- future: trainData.operator,
- builder: (context, operator) {
- return Center(
- child: Text(
- operator,
- style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
- fontSize: 14,
- fontStyle: FontStyle.italic,
- ),
- ),
- );
- },
- ),
- );
- }
-}
-
-class DisplayTrainDeparture extends StatelessWidget {
- final OnDemandTrainData trainData;
-
- DisplayTrainDeparture({@required this.trainData});
-
- @override
- Widget build(BuildContext context) {
- return SliverToBoxAdapter(
- child: Padding(
- padding: const EdgeInsets.all(2),
- child: FutureDisplay(
- 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: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
- fontStyle: FontStyle.italic,
- fontWeight: FontWeight.w200,
- ),
- textAlign: TextAlign.center,
- );
- },
- ),
- ),
- );
- }
-}
-
-class DisplayTrainLastInfo extends StatelessWidget {
- final OnDemandTrainData trainData;
-
- DisplayTrainLastInfo({@required this.trainData});
-
- @override
- Widget build(BuildContext context) {
- return SliverToBoxAdapter(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- 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: [
- Padding(
- padding: const EdgeInsets.all(4),
- child: FutureDisplay(
- future: trainData.lastInfo.station,
- builder: (context, station) {
- return Text(
- station,
- style: CupertinoTheme.of(context).textTheme.textStyle,
- 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: CupertinoTheme.of(context).textTheme.textStyle,
- textAlign: TextAlign.right,
- );
- },
- ),
- ),
- ],
- ),
- FutureDisplay(
- 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,
- );
- },
- ),
- 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: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
- fontSize: 14,
- color: Color.fromRGBO(200, 30, 15, 1),
- ),
- );
- }
- else {
- return Text(
- "${-snapshot.data} minute mai devreme",
- style: CupertinoTheme.of(context).textTheme.textStyle.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();
-
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- 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(
- 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: [
- 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 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();
-
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- 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),
- ),
- FutureDisplay(
- future: trainData.destination.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