Merge pull request #1 from dancojocaru2000/transition_webview_to_api

Transition WebView to API
This commit is contained in:
Kenneth Bruen 2021-08-23 08:00:29 +03:00 committed by Dan Cojocaru
commit e86d424bd2
Signed by: kbruen
GPG key ID: 818A889458EDC937
88 changed files with 4398 additions and 5370 deletions

View file

@ -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)_.

29
analysis_options.yaml Normal file
View file

@ -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

13
android/.gitignore vendored Normal file
View file

@ -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

View file

@ -26,24 +26,28 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
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.info_tren"
minSdkVersion 16
targetSdkVersion 28
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@ -61,7 +65,4 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

View file

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ml.dandevelop.info_tren">
package="xyz.dcdevelop.info_tren">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View file

@ -1,38 +1,40 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ml.dandevelop.info_tren">
<uses-permission android:name="android.permission.INTERNET"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
package="xyz.dcdevelop.infotren">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="Info Tren"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:screenOrientation="portrait">
<!-- Specify that the launch screen should continue being displayed -->
<!-- until Flutter renders its first frame. -->
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />

View file

@ -1,4 +1,4 @@
package ml.dandevelop.info_tren
package xyz.dcdevelop.info_tren
import io.flutter.embedding.android.FlutterActivity

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -1,8 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ml.dandevelop.info_tren">
package="xyz.dcdevelop.info_tren">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View file

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.2.71'
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -14,15 +14,13 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}

View file

@ -1,2 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

View file

@ -1,15 +1,11 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

BIN
fonts/ah/ah-Bold.ttf Normal file

Binary file not shown.

BIN
fonts/ah/ah-BoldItalic.ttf Normal file

Binary file not shown.

BIN
fonts/ah/ah-Italic.ttf Normal file

Binary file not shown.

BIN
fonts/ah/ah-Regular.ttf Normal file

Binary file not shown.

33
ios/.gitignore vendored Normal file
View file

@ -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

View file

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>9.0</string>
</dict>
</plist>

View file

@ -1,2 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View file

@ -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

View file

@ -1,2 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View file

@ -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=="

View file

@ -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

View file

@ -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 = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
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 = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
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 = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
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 = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
@ -47,7 +42,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -87,8 +72,6 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
A2E7A2EB20EFBBAC4AB0299B /* Pods */,
0B24EBF53F1DCC708FA961FD /* Frameworks */,
);
sourceTree = "<group>";
};
@ -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 = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
);
name = "Supporting Files";
sourceTree = "<group>";
};
A2E7A2EB20EFBBAC4AB0299B /* Pods */ = {
isa = PBXGroup;
children = (
313F1E773DA06364A0C4F20A /* Pods-Runner.debug.xcconfig */,
74CD890ACD2E394E606FCBEB /* Pods-Runner.release.xcconfig */,
636963D381657D3BAEDC0A47 /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* 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;

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

@ -4,7 +4,4 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -17,24 +17,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>cfr-scrapper.herokuapp.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@ -42,6 +31,8 @@
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
@ -52,7 +43,5 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict>
</plist>

View file

@ -1 +1 @@
#import "GeneratedPluginRegistrant.h"
#import "GeneratedPluginRegistrant.h"

9
lib/api/train_data.dart Normal file
View file

@ -0,0 +1,9 @@
import 'package:http/http.dart' as http;
import 'package:info_tren/models/train_data.dart';
const AUTHORITY = 'scraper.infotren.dcdevelop.xyz';
Future<TrainData> getTrain(int trainNumber) async {
final response = await http.get(Uri.https(AUTHORITY, 'train/$trainNumber'));
return trainDataFromJson(response.body);
}

View file

@ -0,0 +1,60 @@
import 'package:flutter/cupertino.dart';
import 'package:info_tren/pages/train_info_page/train_info_constants.dart';
class CupertinoDivider extends StatelessWidget {
final Color color;
CupertinoDivider({Key? key, Color? color}):
color = color ?? FOREGROUND_DARK_GREY,
super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 1,
),
Container(
height: 1,
decoration: BoxDecoration(
color: color,
),
),
Container(
height: 1,
),
],
);
}
}
class CupertinoVerticalDivider extends StatelessWidget {
final Color color;
CupertinoVerticalDivider({Key? key, Color? color}):
color = color ?? FOREGROUND_DARK_GREY,
super(key: key);
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 1,
),
Container(
width: 1,
decoration: BoxDecoration(
color: color,
),
),
Container(
width: 1,
),
],
);
}
}

View file

@ -0,0 +1,42 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/utils/default_ui_design.dart';
class FutureDisplay<T> extends StatelessWidget {
final UiDesign? uiDesign;
final Future<T> future;
final Widget Function<T>(BuildContext context, T data) builder;
final Widget Function(BuildContext context, Object error, StackTrace? st)? errorBuilder;
FutureDisplay({Key? key, required this.future, required this.builder, this.errorBuilder, this.uiDesign}): super(key: key);
@override
Widget build(BuildContext context) {
final uiDesign = this.uiDesign ?? defaultUiDesign;
return FutureBuilder(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) return builder(context, snapshot.data);
if (snapshot.hasError) return (errorBuilder != null ? errorBuilder!(context, snapshot.error!, snapshot.stackTrace) : throw snapshot.error!);
if (snapshot.connectionState == ConnectionState.done) return Container();
Widget loadingWidget;
switch (uiDesign) {
case UiDesign.MATERIAL:
loadingWidget = CircularProgressIndicator();
break;
case UiDesign.CUPERTINO:
loadingWidget = CupertinoActivityIndicator();
break;
default:
throw UnmatchedUiDesignException(uiDesign);
}
return Center(
child: loadingWidget,
);
},
);
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,117 @@
import 'package:flutter/widgets.dart';
class RefreshFutureBuilder<T> extends StatefulWidget {
final Future<T> Function()? futureCreator;
final T? initialData;
final Widget Function(BuildContext context, Future Function() refresh, RefreshFutureBuilderSnapshot<T> snapshot) builder;
const RefreshFutureBuilder({ Key? key, this.futureCreator, this.initialData, required this.builder }) : super(key: key);
@override
_RefreshFutureBuilderState<T> createState() => _RefreshFutureBuilderState<T>();
}
class _RefreshFutureBuilderState<T> extends State<RefreshFutureBuilder<T>> {
late RefreshFutureBuilderSnapshot<T> snapshot;
Future<T> Function()? futureCreator;
@override
void initState() {
super.initState();
snapshot = widget.initialData != null ? RefreshFutureBuilderSnapshot.initial(widget.initialData!) : RefreshFutureBuilderSnapshot.nothing();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (futureCreator != widget.futureCreator) {
futureCreator = widget.futureCreator;
runFuture();
}
}
Future runFuture() async {
if (futureCreator == null) {
return;
}
// Set state to signify loading
setState(() {
switch (snapshot.state) {
case RefreshFutureBuilderState.none:
snapshot = RefreshFutureBuilderSnapshot.waiting();
break;
case RefreshFutureBuilderState.initial:
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data);
break;
case RefreshFutureBuilderState.waiting:
return;
case RefreshFutureBuilderState.error:
snapshot = RefreshFutureBuilderSnapshot.refresh(null, snapshot.error, snapshot.stackTrace);
break;
case RefreshFutureBuilderState.done:
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data);
break;
case RefreshFutureBuilderState.refreshing:
return;
case RefreshFutureBuilderState.refreshError:
snapshot = RefreshFutureBuilderSnapshot.refresh(null, snapshot.error, snapshot.stackTrace);
break;
default:
}
});
try {
final data = await futureCreator!();
setState(() {
snapshot = RefreshFutureBuilderSnapshot.withData(data);
});
}
catch (e, st) {
setState(() {
if (snapshot.state == RefreshFutureBuilderState.waiting) {
snapshot = RefreshFutureBuilderSnapshot.withError(e, st);
}
else {
snapshot = RefreshFutureBuilderSnapshot.refreshError(snapshot.data, e, st);
}
});
}
}
@override
Widget build(BuildContext context) {
return widget.builder(
context,
runFuture,
snapshot,
);
}
}
class RefreshFutureBuilderSnapshot<T> {
final RefreshFutureBuilderState state;
final T? data;
final Object? error;
final StackTrace? stackTrace;
bool get hasData => data != null;
bool get hasError => error != null;
const RefreshFutureBuilderSnapshot._(this.state, this.data, this.error, this.stackTrace);
const RefreshFutureBuilderSnapshot.nothing() : state = RefreshFutureBuilderState.none, data = null, error = null, stackTrace = null;
const RefreshFutureBuilderSnapshot.initial(this.data) : state = RefreshFutureBuilderState.initial, error = null, stackTrace = null;
const RefreshFutureBuilderSnapshot.waiting() : state = RefreshFutureBuilderState.waiting, data = null, error = null, stackTrace = null;
const RefreshFutureBuilderSnapshot.withError(this.error, [this.stackTrace]) : state = RefreshFutureBuilderState.error, data = null;
const RefreshFutureBuilderSnapshot.withData(this.data) : state = RefreshFutureBuilderState.done, error = null, stackTrace = null;
const RefreshFutureBuilderSnapshot.refresh(this.data, [this.error, this.stackTrace]) : state = RefreshFutureBuilderState.refreshing;
const RefreshFutureBuilderSnapshot.refreshError(this.data, this.error, this.stackTrace) : state = RefreshFutureBuilderState.refreshError;
}
enum RefreshFutureBuilderState {
none,
initial,
waiting,
error,
done,
refreshing,
refreshError,
}

View file

@ -0,0 +1,209 @@
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_cupertino.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_material.dart';
import 'package:info_tren/models/train_operator_lines.dart';
import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/utils/default_ui_design.dart';
import 'package:tuple/tuple.dart';
class SelectTrainSuggestions extends StatefulWidget {
final UiDesign? uiDesign;
final String userInput;
final void Function(int trainNumber) onTrainSelected;
const SelectTrainSuggestions({ Key? key, required this.uiDesign, required this.userInput, required this.onTrainSelected }) : super(key: key);
@override
SelectTrainSuggestionsState createState() {
final uiDesign = this.uiDesign ?? defaultUiDesign;
switch(uiDesign) {
case UiDesign.MATERIAL:
return SelectTrainSuggestionsStateMaterial();
case UiDesign.CUPERTINO:
return SelectTrainSuggestionsStateCupertino();
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}
abstract class SelectTrainSuggestionsState extends State<SelectTrainSuggestions> {
late String userInput;
List<TrainOperatorLines> operators = [];
Future loadOperators(BuildContext context) async {
operators = [];
final operatorsString = await DefaultAssetBundle.of(context).loadString("assets/lines/files.txt");
final operatorsFilesList = operatorsString.split("\n");
final decoder = JsonDecoder();
for (final operatorFile in operatorsFilesList) {
final operatorString = await DefaultAssetBundle.of(context).loadString("assets/lines/$operatorFile");
final operatorData = decoder.convert(operatorString);
final _operator = TrainOperatorLines.fromJson(operatorData);
operators.add(_operator);
}
}
@override
void initState() {
super.initState();
userInput = widget.userInput;
loadOperators(context).then((_) {
setState(() {});
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (userInput != widget.userInput) {
setState(() {
userInput = widget.userInput;
});
}
}
String getUseCurrentInputWidgetText(int currentInput) => 'Caută trenul cu numărul $currentInput';
Widget getUseCurrentInputWidget(int currentInput, void Function(int) onTrainSelected);
@override
Widget build(BuildContext context) {
var sliversTuple = operators.map(
(op) => Tuple2(
getFilteredLines(op, userInput),
op.operator,
)
).where((tuple) => tuple.item1.isNotEmpty).toList();
if (userInput.isNotEmpty) sliversTuple.sort((a, b) {
final aTrain = a.item1.first;
final bTrain = b.item1.first;
final inputAsRegExp = RegExp(userInput);
final matchOnA = inputAsRegExp.firstMatch(aTrain.number)!;
final matchOnB = inputAsRegExp.firstMatch(bTrain.number)!;
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start;
if (aTrain.number.length != bTrain.number.length) return aTrain.number.length - bTrain.number.length;
return aTrain.number.compareTo(bTrain.number);
});
var slivers = sliversTuple.map((tuple) => OperatorAutocompleteSliver(
uiDesign: widget.uiDesign,
operatorName: tuple.item2,
trains: tuple.item1,
onTrainSelected: widget.onTrainSelected,
)).toList();
return CustomScrollView(
slivers: <Widget>[
...slivers,
SliverToBoxAdapter(
child: int.tryParse(userInput) != null ? getUseCurrentInputWidget(int.parse(userInput), widget.onTrainSelected) : Container(),
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
);
}
List<TrainOperatorTrainDescription> getFilteredLines(TrainOperatorLines _operator, String currentInput) {
if (currentInput.isNotEmpty) {
final filteredLines = _operator.trains
.where((elem) => elem.number.contains(currentInput))
.toList();
filteredLines.sort((a, b) {
final inputAsRegExp = RegExp(currentInput);
final matchOnA = inputAsRegExp.firstMatch(a.number)!;
final matchOnB = inputAsRegExp.firstMatch(b.number)!;
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start;
if (a.number.length != b.number.length) return a.number.length - b.number.length;
return a.number.compareTo(b.number);
});
return filteredLines;
}
else {
return _operator.trains;
}
}
}
class OperatorAutocompleteSliver extends StatelessWidget {
final UiDesign? uiDesign;
final String operatorName;
final List<TrainOperatorTrainDescription> trains;
final void Function(int) onTrainSelected;
const OperatorAutocompleteSliver({ Key? key, required this.uiDesign, required this.operatorName, required this.trains, required this.onTrainSelected }) : super(key: key);
Widget mapTrainToItem(TrainOperatorTrainDescription train) {
final uiDesign = this.uiDesign ?? defaultUiDesign;
switch (uiDesign) {
case UiDesign.MATERIAL:
return OperatorAutocompleteTileMaterial(
onTrainSelected: onTrainSelected,
operatorName: operatorName,
train: train,
);
case UiDesign.CUPERTINO:
return OperatorAutocompleteTileCupertino(
onTrainSelected: onTrainSelected,
operatorName: operatorName,
train: train,
);
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
@override
Widget build(BuildContext context) {
if (trains.isEmpty) {
return SliverToBoxAdapter(child: Container(),);
}
return SliverPrototypeExtentList(
prototypeItem: Column(
children: <Widget>[
mapTrainToItem(TrainOperatorTrainDescription()),
],
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Column(
children: <Widget>[
mapTrainToItem(trains[index]),
],
);
},
childCount: trains.length,
addSemanticIndexes: true,
),
);
}
}
abstract class OperatorAutocompleteTile extends StatelessWidget {
final String operatorName;
final TrainOperatorTrainDescription train;
final void Function(int) onTrainSelected;
const OperatorAutocompleteTile({ Key? key, required this.onTrainSelected, required this.operatorName, required this.train }) : super(key: key);
}

View file

@ -0,0 +1,74 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart';
import 'package:info_tren/models/train_operator_lines.dart';
class SelectTrainSuggestionsStateCupertino extends SelectTrainSuggestionsState {
@override
Widget getUseCurrentInputWidget(int currentInput, void Function(int p1) onTrainSelected) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
onTap: () {
onTrainSelected(currentInput);
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(getUseCurrentInputWidgetText(currentInput)),
],
)
),
),
Divider(),
],
);
}
}
class OperatorAutocompleteTileCupertino extends OperatorAutocompleteTile {
OperatorAutocompleteTileCupertino({
Key? key,
required String operatorName,
required void Function(int) onTrainSelected,
required TrainOperatorTrainDescription train
}): super(
onTrainSelected: onTrainSelected,
operatorName: operatorName,
train: train,
key: key,
);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onTrainSelected(train.internalNumber);
},
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
child: SizedBox(
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
operatorName,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200),
textAlign: TextAlign.left,
),
Text(
"${train.rang} ${train.number}",
textAlign: TextAlign.left,
),
],
),
),
),
);
}
}

View file

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart';
import 'package:info_tren/models/train_operator_lines.dart';
class SelectTrainSuggestionsStateMaterial extends SelectTrainSuggestionsState {
@override
Widget getUseCurrentInputWidget(int currentInput, void Function(int) onTrainSelected) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(getUseCurrentInputWidgetText(currentInput)),
onTap: () {
onTrainSelected(currentInput);
},
),
Divider(),
],
);
}
}
class OperatorAutocompleteTileMaterial extends OperatorAutocompleteTile {
OperatorAutocompleteTileMaterial({
Key? key,
required String operatorName,
required void Function(int) onTrainSelected,
required TrainOperatorTrainDescription train
}): super(
onTrainSelected: onTrainSelected,
operatorName: operatorName,
train: train,
key: key,
);
@override
Widget build(BuildContext context) {
return ListTile(
dense: true,
title: Text("${train.rang} ${train.number}"),
subtitle: Text(operatorName),
onTap: () {
onTrainSelected(train.internalNumber);
},
);
}
}

View file

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
class SlimAppBar extends StatelessWidget {
final String title;
final double size;
// final Function onBackTap;
SlimAppBar({
required this.title,
this.size = 24,
// this.onBackTap,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
height: size,
child: Container(
color:
Theme.of(context).appBarTheme.color ??
Theme.of(context).primaryColor,
child: InkWell(
onTap: (ModalRoute.of(context)?.canPop ?? false)
? () => Navigator.of(context).pop()
: null,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
height: size,
width: size,
child: (ModalRoute.of(context)?.canPop ?? false)
? BackButtonIcon()
: null,
),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Text(
title,
textAlign: TextAlign.center,
style:
Theme.of(context).appBarTheme.textTheme?.caption?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.bodyText2?.color) ??
Theme.of(context).textTheme.caption?.copyWith(color: Theme.of(context).textTheme.bodyText2?.color),
),
),
),
),
Container(
height: size,
width: size,
),
],
),
),
),
);
}
}

View file

@ -1,23 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:webview_flutter/webview_flutter.dart';
class HiddenWebView extends StatelessWidget {
final WebView webView;
final Widget child;
HiddenWebView({@required this.child, this.webView});
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Offstage(
offstage: true,
child: webView,
),
Positioned.fill(child: child)
],
);
}
}

View file

@ -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<String, WidgetBuilder> 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: <Widget>[
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: <Widget>[
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(),
),
),
),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,98 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'train_data.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
TrainData _$TrainDataFromJson(Map<String, dynamic> json) {
return TrainData(
rang: json['rang'] as String,
trainNumber: json['tren'] as String,
operator: json['operator'] as String,
lastInfo: json['ultima_informatie'] == null
? null
: LastInfo.fromJson(
json['ultima_informatie'] as Map<String, dynamic>),
state: json['stare'] as String,
route: json['relatia'] as String,
tripLength: json['durata_calatoriei'] as String,
stations: (json['stations'] as List)
?.map((e) => e == null
? null
: StationEntry.fromJson(e as Map<String, dynamic>))
?.toList(),
nextStop: json['urmatoarea_oprire'] == null
? null
: StopInfo.fromJson(
json['urmatoarea_oprire'] as Map<String, dynamic>),
distance: json['distanta'] as String,
destination: json['destinatie'] == null
? null
: StopInfo.fromJson(json['destinatie'] as Map<String, dynamic>));
}
Map<String, dynamic> _$TrainDataToJson(TrainData instance) => <String, dynamic>{
'rang': instance.rang,
'tren': instance.trainNumber,
'operator': instance.operator,
'relatia': instance.route,
'stare': instance.state,
'ultima_informatie': instance.lastInfo,
'destinatie': instance.destination,
'urmatoarea_oprire': instance.nextStop,
'durata_calatoriei': instance.tripLength,
'distanta': instance.distance,
'stations': instance.stations
};
LastInfo _$LastInfoFromJson(Map<String, dynamic> json) {
return LastInfo(
dateAndTime: json['data_si_ora'] as String,
delay: json['intarziere'] as int,
event: json['eveniment'] as String,
station: json['statia'] as String);
}
Map<String, dynamic> _$LastInfoToJson(LastInfo instance) => <String, dynamic>{
'statia': instance.station,
'eveniment': instance.event,
'data_si_ora': instance.dateAndTime,
'intarziere': instance.delay
};
StopInfo _$StopInfoFromJson(Map<String, dynamic> json) {
return StopInfo(
station: json['statia'] as String,
dateAndTime: json['data_si_ora'] as String);
}
Map<String, dynamic> _$StopInfoToJson(StopInfo instance) => <String, dynamic>{
'statia': instance.station,
'data_si_ora': instance.dateAndTime
};
StationEntry _$StationEntryFromJson(Map<String, dynamic> json) {
return StationEntry(
name: json['statia'] as String,
delay: json['intarziere'] as int,
realOrEstimate: json['real/estimat'] as String,
arrivalTime: json['sosire'] as String,
departureTime: json['plecare'] as String,
km: json['km'] as String,
observations: json['observatii'] as String,
waitTime: json['stationeaza_pentru'] as String);
}
Map<String, dynamic> _$StationEntryToJson(StationEntry instance) =>
<String, dynamic>{
'km': instance.km,
'statia': instance.name,
'sosire': instance.arrivalTime,
'stationeaza_pentru': instance.waitTime,
'plecare': instance.departureTime,
'real/estimat': instance.realOrEstimate,
'intarziere': instance.delay,
'observatii': instance.observations
};

View file

@ -0,0 +1,42 @@
import 'package:json_annotation/json_annotation.dart';
part 'train_operator_lines.g.dart';
@JsonSerializable()
class TrainOperatorLines {
@JsonKey(name: "short_name")
final String shortName;
final String operator;
@JsonKey(name: "versiune")
final String version;
@JsonKey(name: "trenuri")
final List<TrainOperatorTrainDescription> trains;
TrainOperatorLines({
required this.operator,
this.shortName = "",
required this.version,
required this.trains,
});
factory TrainOperatorLines.fromJson(Map<String, dynamic> json) => _$TrainOperatorLinesFromJson(json);
Map<String, dynamic> toJson() => _$TrainOperatorLinesToJson(this);
}
@JsonSerializable()
class TrainOperatorTrainDescription {
final String rang;
@JsonKey(name: "numar")
final String number;
@JsonKey(name: "numar_intern")
final int internalNumber;
TrainOperatorTrainDescription({
this.number = '',
this.rang = '',
this.internalNumber = 0,
});
factory TrainOperatorTrainDescription.fromJson(Map<String, dynamic> json) => _$TrainOperatorTrainDescriptionFromJson(json);
Map<String, dynamic> toJson() => _$TrainOperatorTrainDescriptionToJson(this);
}

View file

@ -0,0 +1,42 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'train_operator_lines.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
TrainOperatorLines _$TrainOperatorLinesFromJson(Map<String, dynamic> json) =>
TrainOperatorLines(
operator: json['operator'] as String,
shortName: json['short_name'] as String? ?? "",
version: json['versiune'] as String,
trains: (json['trenuri'] as List<dynamic>)
.map((e) =>
TrainOperatorTrainDescription.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$TrainOperatorLinesToJson(TrainOperatorLines instance) =>
<String, dynamic>{
'short_name': instance.shortName,
'operator': instance.operator,
'versiune': instance.version,
'trenuri': instance.trains,
};
TrainOperatorTrainDescription _$TrainOperatorTrainDescriptionFromJson(
Map<String, dynamic> json) =>
TrainOperatorTrainDescription(
number: json['numar'] as String? ?? '',
rang: json['rang'] as String? ?? '',
internalNumber: json['numar_intern'] as int? ?? 0,
);
Map<String, dynamic> _$TrainOperatorTrainDescriptionToJson(
TrainOperatorTrainDescription instance) =>
<String, dynamic>{
'rang': instance.rang,
'numar': instance.number,
'numar_intern': instance.internalNumber,
};

15
lib/models/ui_design.dart Normal file
View file

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

View file

@ -0,0 +1,68 @@
import 'package:flutter/widgets.dart';
import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/pages/main/main_page_cupertino.dart';
import 'package:info_tren/pages/main/main_page_material.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
import 'package:info_tren/utils/default_ui_design.dart';
class MainPage extends StatelessWidget {
final UiDesign? uiDesign;
const MainPage({ Key? key, this.uiDesign }) : super(key: key);
@override
Widget build(BuildContext context) {
final uiDesign = this.uiDesign ?? defaultUiDesign;
switch (uiDesign) {
case UiDesign.MATERIAL:
return MainPageMaterial();
case UiDesign.CUPERTINO:
return MainPageCupertino();
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}
abstract class MainPageShared extends StatelessWidget {
final String pageTitle = 'Info Tren';
List<MainPageOption> get options => [
MainPageOption(
name: 'Informații despre tren',
action: (BuildContext context) {
onTrainInfoPageInvoke(context);
},
),
MainPageOption(
name: 'Tabelă plecari/sosiri',
// TODO: Implement departure/arrival
action: null,
),
MainPageOption(
name: 'Planificare rută',
// TODO: Implement route planning
action: null,
),
];
onTrainInfoPageInvoke(BuildContext context) {
Navigator.of(context).pushNamed(SelectTrainPage.routeName);
}
onStationBoardPageInvoke(BuildContext context) {
}
onRoutePlanPageInvoke(BuildContext context) {
}
}
class MainPageOption {
final String name;
final void Function(BuildContext context)? action;
MainPageOption({required this.name, this.action});
}

View file

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

View file

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

View file

@ -0,0 +1,61 @@
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart';
import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train_cupertino.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train_material.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart';
import 'package:info_tren/utils/default_ui_design.dart';
import 'package:tuple/tuple.dart';
typedef TrainSelectedCallback(int trainNumber);
class SelectTrainPage extends StatefulWidget {
final UiDesign? uiDesign;
SelectTrainPage({Key? key, this.uiDesign}) : super(key: key);
static String routeName = "/trainInfo/selectTrain";
void onTrainSelected(BuildContext context, int selection) {
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: selection);
}
@override
SelectTrainPageState createState() {
final uiDesign = this.uiDesign ?? defaultUiDesign;
switch(uiDesign) {
case UiDesign.MATERIAL:
return SelectTrainPageStateMaterial();
case UiDesign.CUPERTINO:
return SelectTrainPageStateCupertino();
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}
abstract class SelectTrainPageState extends State<SelectTrainPage> {
final String pageTitle = 'Informații despre tren';
final String textFieldLabel = 'Numărul trenului';
TextEditingController trainNoController = TextEditingController();
@override
void initState() {
super.initState();
}
void onTextChanged() {
setState(() {});
}
Widget get suggestionsList => SelectTrainSuggestions(
uiDesign: widget.uiDesign,
userInput: trainNoController.text,
onTrainSelected: (trainNumber) => widget.onTrainSelected(context, trainNumber),
key: ValueKey(trainNoController.text),
);
}

View file

@ -0,0 +1,39 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
class SelectTrainPageStateCupertino extends SelectTrainPageState {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(pageTitle),
),
child: SafeArea(
bottom: false,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: CupertinoTextField(
controller: trainNoController,
autofocus: true,
placeholder: textFieldLabel,
textInputAction: TextInputAction.search,
keyboardType: TextInputType.number,
onChanged: (_) => onTextChanged(),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
),
),
Expanded(
child: suggestionsList,
),
],
),
),
);
}
}

View file

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
class SelectTrainPageStateMaterial extends SelectTrainPageState {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(pageTitle),
centerTitle: true,
),
body: SafeArea(
bottom: false,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: TextField(
controller: trainNoController,
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: textFieldLabel,
),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
textInputAction: TextInputAction.search,
keyboardType: TextInputType.number,
onChanged: (_) => onTextChanged(),
),
),
Expanded(
child: suggestionsList,
),
],
),
),
);
}
}

View file

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

View file

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:info_tren/api/train_data.dart';
import 'package:info_tren/components/loading/loading.dart';
import 'package:info_tren/components/refresh_future_builder.dart';
import 'package:info_tren/models/train_data.dart';
import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart';
import 'package:info_tren/utils/default_ui_design.dart';
class TrainInfo extends StatelessWidget {
static String routeName = "/trainInfo/display";
final UiDesign? uiDesign;
final int trainNumber;
TrainInfo({Key? key, required this.trainNumber, this.uiDesign}): super(key: key);
@override
Widget build(BuildContext context) {
final uiDesign = this.uiDesign ?? defaultUiDesign;
return RefreshFutureBuilder<TrainData>(
futureCreator: () => getTrain(trainNumber),
builder: (context, refresh, snapshot) {
switch (uiDesign) {
case UiDesign.MATERIAL:
if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) {
return TrainInfoLoadingMaterial(title: trainNumber.toString(),);
}
else if (snapshot.state == RefreshFutureBuilderState.error) {
return TrainInfoErrorMaterial(title: '$trainNumber - Error', error: snapshot.error!,);
}
return TrainInfoMaterial(trainData: snapshot.data!,);
case UiDesign.CUPERTINO:
if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) {
return TrainInfoLoadingCupertino(title: trainNumber.toString(),);
}
else if (snapshot.state == RefreshFutureBuilderState.error) {
return TrainInfoErrorCupertino(title: '$trainNumber - Error', error: snapshot.error!,);
}
return TrainInfoCupertino(trainData: snapshot.data!,);
default:
throw UnmatchedUiDesignException(uiDesign);
}
},
);
}
}
abstract class TrainInfoLoading extends StatelessWidget {
final String title;
final Widget loadingWidget;
TrainInfoLoading({required this.title, String? loadingText, UiDesign? uiDesign}) : loadingWidget = Loading(uiDesign: uiDesign, text: loadingText,);
}
abstract class TrainInfoError extends StatelessWidget {
final String title;
final Object error;
TrainInfoError({required this.title, required this.error});
}

View file

@ -0,0 +1,741 @@
import 'package:flutter/cupertino.dart';
import 'package:info_tren/components/cupertino_divider.dart';
import 'package:info_tren/models/train_data.dart' hide State;
import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/pages/train_info_page/train_info_constants.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart';
import 'package:info_tren/utils/state_to_string.dart';
class TrainInfoLoadingCupertino extends TrainInfoLoading {
TrainInfoLoadingCupertino({required String title, String? loadingText}) : super(title: title, loadingText: loadingText, uiDesign: UiDesign.CUPERTINO);
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(title),
),
child: Center(
child: loadingWidget,
),
);
}
}
class TrainInfoErrorCupertino extends TrainInfoError {
TrainInfoErrorCupertino({required Object error, required String title,}) : super(error: error, title: title,);
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(title),
),
child: Center(
child: Text(error.toString()),
),
);
}
}
class TrainInfoCupertino extends StatelessWidget {
final TrainData trainData;
TrainInfoCupertino({required this.trainData});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Informații despre ${trainData.rank} ${trainData.number}"),
),
child: SafeArea(
top: false,
bottom: false,
child: Builder(
builder: (context) {
final topPadding = MediaQuery.of(context).padding.top;
return CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.only(
top: topPadding,
),
child: Container(),
),
),
DisplayTrainID(trainData: trainData,),
DisplayTrainOperator(trainData: trainData,),
DisplayTrainRoute(trainData: trainData,),
DisplayTrainDeparture(trainData: trainData,),
SliverToBoxAdapter(
child: CupertinoDivider(
color: FOREGROUND_WHITE,
),
),
DisplayTrainLastInfo(trainData: trainData,),
SliverToBoxAdapter(
child: CupertinoDivider(),
),
SliverToBoxAdapter(
child: IntrinsicHeight(
child: Row(
children: <Widget>[
// Expanded(
// child: DisplayTrainNextStop(trainData: trainData,),
// ),
Expanded(
child: DisplayTrainDestination(trainData: trainData,),
),
SizedBox(
height: double.infinity,
child: CupertinoVerticalDivider(),
),
Expanded(child: DisplayTrainRouteDistance(trainData: trainData,),),
],
),
),
),
// SliverToBoxAdapter(
// child: CupertinoDivider(),
// ),
// SliverToBoxAdapter(
// child: IntrinsicHeight(
// child: Row(
// children: <Widget>[
// // Expanded(
// // child: DisplayTrainRouteDuration(trainData: trainData,),
// // ),
// Expanded(child: Container(),),
// SizedBox(
// height: double.infinity,
// child: CupertinoVerticalDivider(),
// ),
// Expanded(
// child: DisplayTrainRouteDistance(trainData: trainData,),
// )
// ],
// ),
// ),
// ),
SliverToBoxAdapter(
child: CupertinoDivider(
color: FOREGROUND_WHITE,
),
),
DisplayTrainStations(
trainData: trainData,
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
);
}
),
),
);
// return CupertinoPageScaffold(
// navigationBar: CupertinoNavigationBar(
// middle: Text(title ?? ""),
// ),
// child: SafeArea(
// bottom: false,
// child: FutureBuilder<OnDemandTrainData>(
// future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () {
// Navigator.of(context).pop();
// }),
// builder: (context, snapshot) {
// if (!snapshot.hasData) {
// return Center(
// child: CupertinoActivityIndicator(),
// );
// }
// try {
// Future.wait([
// snapshot.data.rang,
// snapshot.data.trainNumber
// ]).then((values) {
// setState(() {
// title = "Informații despre ${values[0]} ${values[1]}";
// });
// });
// return CustomScrollView(
// slivers: <Widget>[
// DisplayTrainID(data: snapshot.data,),
// DisplayTrainOperator(data: snapshot.data,),
// DisplayTrainRoute(data: snapshot.data,),
// DisplayTrainDeparture(data: snapshot.data,),
// SliverToBoxAdapter(
// child: CupertinoDivider(
// color: FOREGROUND_WHITE,
// ),
// ),
// DisplayTrainLastInfo(data: snapshot.data,),
// SliverToBoxAdapter(
// child: CupertinoDivider(),
// ),
// SliverToBoxAdapter(
// child: IntrinsicHeight(
// child: Row(
// children: <Widget>[
// Expanded(
// child: DisplayTrainNextStop(data: snapshot.data,),
// ),
// SizedBox(
// height: double.infinity,
// child: CupertinoVerticalDivider(),
// ),
// Expanded(
// child: DisplayTrainDestination(data: snapshot.data,),
// )
// ],
// ),
// ),
// ),
// SliverToBoxAdapter(
// child: CupertinoDivider(),
// ),
// SliverToBoxAdapter(
// child: IntrinsicHeight(
// child: Row(
// children: <Widget>[
// Expanded(
// child: DisplayTrainRouteDuration(data: snapshot.data,),
// ),
// SizedBox(
// height: double.infinity,
// child: CupertinoVerticalDivider(),
// ),
// Expanded(
// child: DisplayTrainRouteDistance(data: snapshot.data,),
// )
// ],
// ),
// ),
// ),
// SliverToBoxAdapter(
// child: CupertinoDivider(
// color: FOREGROUND_WHITE,
// ),
// ),
// DisplayTrainStations(
// data: snapshot.data,
// pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture,
// ),
// ],
// );
// }
// on OnDemandInvalidatedException {
// Navigator.of(context).pop();
// print("Got OnDemandInvalidatedException!");
// return Container();
// }
// },
// ),
// ),
// );
}
}
class DisplayTrainID extends StatelessWidget {
final TrainData trainData;
DisplayTrainID({required this.trainData});
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"${trainData.rank} ${trainData.number}",
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
),
),
),
);
}
}
class DisplayTrainRoute extends StatelessWidget {
final TrainData trainData;
DisplayTrainRoute({required this.trainData});
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Row(
children: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
trainData.route.from,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 16,
),
),
),
),
Expanded(child: Container(),),
Center(child: Text("-")),
Expanded(child: Container(),),
Center(
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
trainData.route.to,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 16,
),
textAlign: TextAlign.right,
),
),
),
],
),
);
}
}
class DisplayTrainOperator extends StatelessWidget {
final TrainData trainData;
DisplayTrainOperator({required this.trainData});
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Center(
child: Text(
trainData.operator,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 14,
fontStyle: FontStyle.italic,
),
),
),
);
}
}
class DisplayTrainDeparture extends StatelessWidget {
final TrainData trainData;
DisplayTrainDeparture({required this.trainData});
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(2),
child: Text(
// "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}",
"Plecare în ${trainData.date}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w200,
),
textAlign: TextAlign.center,
),
),
);
}
}
class DisplayTrainLastInfo extends StatelessWidget {
final TrainData trainData;
DisplayTrainLastInfo({required this.trainData});
@override
Widget build(BuildContext context) {
if (trainData.status == null) {
return SliverToBoxAdapter(child: Container(),);
}
return SliverToBoxAdapter(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Text(
"Ultima informație",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: Text(
trainData.status!.station,
style: CupertinoTheme.of(context).textTheme.textStyle,
textAlign: TextAlign.left,
),
),
Expanded(child: Container(),),
Padding(
padding: const EdgeInsets.all(4),
child: Text(
stateToString(trainData.status!.state),
style: CupertinoTheme.of(context).textTheme.textStyle,
textAlign: TextAlign.right,
),
),
],
),
// FutureDisplay<DateTime>(
// future: trainData.lastInfo.dateAndTime,
// builder: (context, dt) {
// return Text(
// "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}",
// textAlign: TextAlign.center,
// );
// },
// ),
Builder(
builder: (context) {
final data = trainData.status!.delay;
if (data == 0) {
return Container();
}
if (data > 0) {
return Text(
"$data minute întârziere",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 14,
color: CupertinoColors.destructiveRed,
),
);
}
else {
return Text(
"${-data} minute mai devreme",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 12,
color: CupertinoColors.activeGreen,
),
);
}
},
)
],
),
);
}
}
// class DisplayTrainNextStop extends StatelessWidget {
// final TrainData trainData;
//
// DisplayTrainNextStop({required this.trainData});
//
// @override
// Widget build(BuildContext context) {
// return FutureBuilder(
// future: trainData.nextStop.stationName,
// builder: (context, snapshot) {
// if (!snapshot.hasData) return Container();
//
// return Column(
// mainAxisSize: MainAxisSize.min,
// children: <Widget>[
// Padding(
// padding: const EdgeInsets.all(4),
// child: Text(
// "Următoarea oprire",
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// ),
// textAlign: TextAlign.center,
// ),
// ),
// CupertinoDivider(
// color: Color.fromRGBO(15, 15, 15, 1),
// ),
// FutureDisplay(
// future: trainData.nextStop.stationName,
// builder: (context, station) {
// return Padding(
// padding: const EdgeInsets.fromLTRB(4, 0, 4, 0),
// child: Text(
// station,
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
// fontSize: 18,
// fontWeight: FontWeight.w500,
// ),
// textAlign: TextAlign.center,
// ),
// );
// },
// ),
// FutureDisplay<DateTime>(
// future: trainData.nextStop.arrival,
// builder: (context, arrival) {
// const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"];
//
// return Column(
// mainAxisSize: MainAxisSize.min,
// children: <Widget>[
// Text(
// "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}",
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
// fontSize: 14,
// ),
// textAlign: TextAlign.center,
// ),
// Text(
// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}",
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
// fontSize: 14,
// ),
// textAlign: TextAlign.center,
// ),
// ],
// );
// },
// )
// ],
// );
// }
// );
// }
// }
class DisplayTrainDestination extends StatelessWidget {
final TrainData trainData;
DisplayTrainDestination({required this.trainData});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: Text(
"Destinația",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 20,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
CupertinoDivider(
color: Color.fromRGBO(15, 15, 15, 1),
),
Padding(
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0),
child: Text(
trainData.stations.last.name,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 18,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
Builder(
builder: (context) {
final arrival = trainData.stations.last.arrival!.scheduleTime;
final delay = trainData.stations.last.arrival!.status?.delay ?? 0;
final parts = arrival.split(':');
final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1]));
final arrivalWithDelay = arrivalDT.add(Duration(minutes: delay));
final arrivalWithDelayString = '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}';
// const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"];
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// Text(
// "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}",
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
// fontSize: 14,
// ),
// textAlign: TextAlign.center,
// ),
Text.rich(
TextSpan(
text: 'la',
children: [
TextSpan(text: ' '),
TextSpan(
text: '$arrival',
style: delay == 0 ? null : TextStyle(
decoration: TextDecoration.lineThrough,
),
),
if (delay != 0) ...[
TextSpan(text: ' '),
TextSpan(
text: '$arrivalWithDelayString',
style: TextStyle(
color: delay > 0 ? CupertinoColors.destructiveRed : CupertinoColors.activeGreen,
),
),
]
],
),
// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
);
},
)
],
);
}
}
class DisplayTrainRouteDistance extends StatelessWidget {
final TrainData trainData;
DisplayTrainRouteDistance({required this.trainData});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"Distanța rutei",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
Text(
"${trainData.stations.last.km} km",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 16,
),
textAlign: TextAlign.center,
),
],
);
}
}
// class DisplayTrainRouteDuration extends StatelessWidget {
// final TrainData trainData;
//
// DisplayTrainRouteDuration({required this.trainData});
//
// @override
// Widget build(BuildContext context) {
// return Column(
// mainAxisSize: MainAxisSize.min,
// children: <Widget>[
// Text(
// "Durata rutei",
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// ),
// textAlign: TextAlign.center,
// ),
// FutureDisplay<Duration>(
// future: trainData.routeDuration,
// builder: (context, duration) {
// var durationString = StringBuffer();
//
// bool firstWritten = false;
//
// if (duration.inDays > 0) {
// firstWritten = true;
// if (duration.inDays == 1) durationString.write("1 zi");
// else durationString.write("${duration.inDays} zile");
// duration -= Duration(days: duration.inDays);
// }
//
// if (duration.inHours > 0) {
// if (firstWritten) {
// durationString.write(", ");
// }
// firstWritten = true;
// if (duration.inHours == 1) durationString.write("1 oră");
// else durationString.write("${duration.inHours} ore");
// duration -= Duration(hours: duration.inHours);
// }
//
// if (duration.inMinutes > 0) {
// if (firstWritten) {
// durationString.write(", ");
// }
// firstWritten = true;
// if (duration.inMinutes == 1) durationString.write("1 minut");
// else durationString.write("${duration.inMinutes} minute");
// duration -= Duration(minutes: duration.inMinutes);
// }
//
// return Text(
// durationString.toString(),
// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
// fontSize: 16,
// ),
// textAlign: TextAlign.center,
// );
// },
// ),
// ],
// );
// }
// }
class DisplayTrainStations extends StatelessWidget {
final TrainData trainData;
DisplayTrainStations({required this.trainData,});
@override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index.isOdd) {
return CupertinoDivider();
}
else {
final itemIndex = index ~/ 2;
return IndexedSemantics(
child: DisplayTrainStation(
station: trainData.stations[itemIndex],
),
index: itemIndex,
);
}
},
childCount: trainData.stations.length * 2 - 1,
addSemanticIndexes: false,
),
);
}
}

View file

@ -0,0 +1,466 @@
import 'package:flutter/cupertino.dart';
import 'package:info_tren/models/train_data.dart';
import 'package:info_tren/pages/train_info_page/train_info_constants.dart';
class DisplayTrainStation extends StatelessWidget {
final Station station;
DisplayTrainStation({required this.station});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Builder(
builder: (context) {
final delay = station.departure?.status?.delay ?? station.arrival?.status?.delay;
final real = station.departure?.status?.real ?? station.arrival?.status?.real;
final isDelayed = delay != null && delay > 0 && real == true;
final isOnTime = delay != null && delay <= 0 && real == true;
final isNotScheduled = false;
return KmBadge(
station: station,
isNotScheduled: isNotScheduled,
isDelayed: isDelayed,
isOnTime: isOnTime,
);
}
),
Expanded(
child: Title(
station: station,
),
)
],
),
Time(
station: station,
),
Delay(
station: station,
),
],
);
}
}
class KmBadge extends StatelessWidget {
final Station station;
final bool isNotScheduled;
final bool isOnTime;
final bool isDelayed;
KmBadge({
required this.station,
this.isNotScheduled = false,
this.isOnTime = false,
this.isDelayed = false,
});
@override
Widget build(BuildContext context) {
Color foregroundColor = FOREGROUND_WHITE;
Color? backgroundColor;
if (isNotScheduled) {
foregroundColor = Color.fromRGBO(225, 175, 30, 1);
backgroundColor = Color.fromRGBO(80, 40, 10, 1);
}
else if (isOnTime) {
foregroundColor = Color.fromRGBO(130, 175, 65, 1);
backgroundColor = Color.fromRGBO(40, 80, 10, 1);
}
else if (isDelayed) {
foregroundColor = Color.fromRGBO(225, 75, 30, 1);
backgroundColor = Color.fromRGBO(80, 20, 10, 1);
}
return Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 2,
color: foregroundColor,
),
color: backgroundColor,
// color: CupertinoColors.activeOrange,
),
width: 48,
height: 48,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Center(
child: Text(
station.km.toString(),
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 20,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor,
),
textAlign: TextAlign.center,
),
),
),
Text(
"km",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 10,
color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor,
),
),
],
),
),
);
}
}
class Title extends StatelessWidget {
final Station station;
Title({
required this.station
});
@override
Widget build(BuildContext context) {
return Text(
station.name,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w500 : FontWeight.w300,
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal,
),
textAlign: TextAlign.center,
);
}
}
class Time extends StatelessWidget {
final Station station;
Time({
required this.station,
});
@override
Widget build(BuildContext context) {
if (station.arrival == null) {
// Plecare
return DepartureTime(
station: station,
firstStation: true,
);
}
if (station.departure == null) {
// Sosire
return ArrivalTime(
station: station,
finalStation: true,
);
}
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
),
),
Container(width: 2,),
ArrivalTime(station: station,),
Expanded(child: Container(),),
StopTime(station: station,),
Expanded(child: Container(),),
DepartureTime(station: station,),
Container(width: 2,),
Text(
"",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
),
),
],
);
}
}
class ArrivalTime extends StatelessWidget {
final Station station;
final bool finalStation;
ArrivalTime({
required this.station,
this.finalStation = false,
});
@override
Widget build(BuildContext context) {
if (finalStation) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
),
),
Container(width: 2,),
Text("sosire la "),
ArrivalTime(station: station,),
Expanded(child: Container(),),
],
);
}
else {
final delay = station.arrival!.status?.delay ?? 0;
final time = station.arrival!.scheduleTime;
if (delay == 0) {
return Text("$time");
}
else if (delay > 0) {
final splits = time.split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final newDate = oldDate.add(Duration(minutes: delay));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed,
),
),
],
);
}
else {
final splits = time.split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final newDate = oldDate.subtract(Duration(minutes: delay));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.activeGreen,
),
),
],
);
}
}
}
}
class StopTime extends StatelessWidget {
final Station station;
StopTime({
required this.station,
});
@override
Widget build(BuildContext context) {
final stopsFor = station.stoppingTime!;
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"staționează pentru",
textAlign: TextAlign.center,
),
Builder(
builder: (context) {
int stopsForInt = stopsFor;
if (stopsForInt == 1) {
return Text(
"1 minut",
textAlign: TextAlign.center,
);
}
else if (stopsForInt < 20) {
return Text(
"$stopsFor minute",
textAlign: TextAlign.center,
);
}
else {
return Text(
"$stopsFor de minute",
textAlign: TextAlign.center,
);
}
},
)
],
);
}
}
class DepartureTime extends StatelessWidget {
final Station station;
final bool firstStation;
DepartureTime({
required this.station,
this.firstStation = false,
});
@override
Widget build(BuildContext context) {
if (firstStation) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(child: Container(),),
Text("plecare la "),
DepartureTime(station: station,),
Container(width: 2,),
Text(
"",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
),
),
],
);
}
else {
final delay = station.departure!.status?.delay ?? 0;
final time = station.departure!.scheduleTime;
if (delay == 0) {
return Text("$time");
}
else if (delay > 0) {
final splits = time.split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final newDate = oldDate.add(Duration(minutes: delay));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed,
),
),
],
);
}
else {
final splits = time.split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final newDate = oldDate.subtract(Duration(minutes: delay));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.activeGreen,
),
),
],
);
}
}
}
}
class Delay extends StatelessWidget {
final Station station;
Delay({
required this.station,
});
@override
Widget build(BuildContext context) {
if (station.arrival?.status == null && station.departure?.status == null) {
return Container();
}
var delay = station.arrival?.status?.delay;
if (station.departure?.status?.real == true) {
delay = station.departure?.status?.delay;
}
if (delay == 0 || delay == null) return Container();
else if (delay > 0) {
return Text(
"$delay minute întârziere",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed,
fontSize: 14,
fontStyle: FontStyle.italic,
),
);
}
else if (delay < 0) {
return Text(
"${-delay} minute mai devreme",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.activeGreen,
fontSize: 14,
fontStyle: FontStyle.italic,
),
);
}
return Container();
}
}

View file

@ -0,0 +1,659 @@
import 'package:flutter/material.dart';
import 'package:info_tren/components/slim_app_bar.dart';
import 'package:info_tren/models/train_data.dart' hide State;
import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart';
import 'package:info_tren/utils/state_to_string.dart';
class TrainInfoLoadingMaterial extends TrainInfoLoading {
TrainInfoLoadingMaterial({required String title, String? loadingText}) : super(title: title, loadingText: loadingText, uiDesign: UiDesign.MATERIAL);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: loadingWidget,
),
);
}
}
class TrainInfoErrorMaterial extends TrainInfoError {
TrainInfoErrorMaterial({required Object error, required String title,}) : super(error: error, title: title,);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Text(error.toString()),
),
);
}
}
bool isSmallScreen(BuildContext context) => MediaQuery.of(context).size.height <= 425;
class TrainInfoMaterial extends StatelessWidget {
final TrainData trainData;
TrainInfoMaterial({required this.trainData});
@override
Widget build(BuildContext context) {
return Builder(
builder: (context) {
return Scaffold(
appBar: isSmallScreen(context) ? null : AppBar(
centerTitle: true,
title: Text("Informații despre ${trainData.rank} ${trainData.number}"),
),
body: Column(
children: <Widget>[
if (isSmallScreen(context))
SlimAppBar(
title: 'INFO TREN - ${trainData.rank} ${trainData.number}'
),
Expanded(
child: SafeArea(
bottom: false,
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: DisplayTrainID(trainData: trainData,),
),
SliverToBoxAdapter(
child: DisplayTrainOperator(trainData: trainData,),
),
SliverPadding(
padding: const EdgeInsets.only(left: 2, right: 2),
sliver: SliverToBoxAdapter(
child: DisplayTrainRoute(trainData: trainData,),
),
),
SliverToBoxAdapter(
child: DisplayTrainDeparture(trainData: trainData,),
),
// SliverToBoxAdapter(
// child: Divider(
// color: Colors.white70,
// height: isSmallScreen(context) ? 8 : 16,
// ),
// ),
SliverToBoxAdapter(
child: DisplayTrainLastInfo(trainData: trainData,),
),
SliverToBoxAdapter(
child: IntrinsicHeight(
child: Row(
children: <Widget>[
// Expanded(child: DisplayTrainNextStop(trainData: trainData,)),
Expanded(child: DisplayTrainDestination(trainData: trainData,)),
Expanded(child: DisplayTrainRouteDistance(trainData: trainData,),),
],
),
),
),
// SliverToBoxAdapter(
// child: IntrinsicHeight(
// child: Row(
// children: <Widget>[
// // Expanded(child: DisplayTrainRouteDuration(trainData: trainData,)),
// Expanded(child: Container(),),
// Expanded(child: DisplayTrainRouteDistance(trainData: trainData,)),
// ],
// ),
// ),
// ),
SliverToBoxAdapter(
child: Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
),
DisplayTrainStations(
trainData: trainData,
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
),
),
),
],
),
);
},
);
}
}
class DisplayTrainID extends StatelessWidget {
final TrainData trainData;
DisplayTrainID({required this.trainData});
@override
Widget build(BuildContext context) {
return Text(
"${trainData.rank} ${trainData.number}",
style: (isSmallScreen(context)
? Theme.of(context).textTheme.headline4
: Theme.of(context).textTheme.headline3)?.copyWith(
color: Theme.of(context).textTheme.bodyText2?.color,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
);
}
}
class DisplayTrainOperator extends StatelessWidget {
final TrainData trainData;
DisplayTrainOperator({required this.trainData});
@override
Widget build(BuildContext context) {
return Text(
trainData.operator,
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontStyle: FontStyle.italic,
fontSize: isSmallScreen(context) ? 12 : 14,
),
textAlign: TextAlign.center,
);
}
}
class DisplayTrainRoute extends StatelessWidget {
final TrainData trainData;
DisplayTrainRoute({required this.trainData});
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
trainData.route.from,
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: 16,
),
),
),
),
Expanded(child: Container(),),
Center(child: Text("-")),
Expanded(child: Container(),),
Center(
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
trainData.route.to,
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: 16,
),
textAlign: TextAlign.right,
),
),
),
],
);
}
}
class DisplayTrainDeparture extends StatelessWidget {
final TrainData trainData;
DisplayTrainDeparture({required this.trainData});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(2),
child: Text(
// "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}",
"Plecare în ${trainData.date}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w200,
fontSize: isSmallScreen(context) ? 14 : 16,
),
textAlign: TextAlign.center,
),
);
}
}
class DisplayTrainLastInfo extends StatelessWidget {
final TrainData trainData;
DisplayTrainLastInfo({required this.trainData});
@override
Widget build(BuildContext context) {
if (trainData.status == null) {
return Container();
}
return Card(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Text(
"Ultima informație",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 20 : 22,
fontWeight: FontWeight.bold,
),
),
),
),
Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: Text(
trainData.status!.station,
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 16 : 18,
),
textAlign: TextAlign.left,
),
),
Expanded(child: Container(),),
Padding(
padding: const EdgeInsets.all(4),
child: Text(
stateToString(trainData.status!.state),
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 16 : 18,
),
textAlign: TextAlign.right,
),
),
],
),
Padding(
padding: const EdgeInsets.all(2),
child: Row(
children: <Widget>[
// FutureDisplay<DateTime>(
// future: trainData.lastInfo.dateAndTime,
// builder: (context, dt) {
// return Text(
// "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}",
// textAlign: TextAlign.center,
// );
// },
// ),
Expanded(child: Container(),),
Builder(
builder: (context) {
final data = trainData.status!.delay;
if (data == 0) {
return Container();
}
if (data > 0) {
return Text(
"$data minute întârziere",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 14 : 16,
color: Colors.red.shade300,
),
);
}
else {
return Text(
"${-data} minute mai devreme",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 14 : 16,
color: Colors.green.shade300,
),
);
}
},
),
],
),
),
],
),
),
);
}
}
// class DisplayTrainNextStop extends StatelessWidget {
// final OnDemandTrainData trainData;
//
// DisplayTrainNextStop({@required this.trainData});
//
// @override
// Widget build(BuildContext context) {
// return FutureBuilder(
// future: trainData.nextStop.stationName,
// builder: (context, snapshot) {
// if (!snapshot.hasData) return Container(height: 0,);
//
// return Card(
// child: Center(
// child: Padding(
// padding: const EdgeInsets.all(2),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: <Widget>[
// Padding(
// padding: const EdgeInsets.all(4),
// child: Text(
// "Următoarea oprire",
// style: Theme.of(context).textTheme.bodyText2.copyWith(
// fontSize: isSmallScreen(context) ? 18 : 20,
// fontWeight: FontWeight.bold,
// ),
// textAlign: TextAlign.center,
// ),
// ),
// FutureDisplay(
// future: trainData.nextStop.stationName,
// builder: (context, station) {
// return Padding(
// padding: const EdgeInsets.fromLTRB(4, 0, 4, 0),
// child: Text(
// station,
// style: Theme.of(context).textTheme.bodyText2.copyWith(
// fontSize: isSmallScreen(context) ? 16 : 18,
// fontWeight: FontWeight.w500,
// ),
// textAlign: TextAlign.center,
// ),
// );
// },
// ),
// FutureDisplay<DateTime>(
// future: trainData.nextStop.arrival,
// builder: (context, arrival) {
// const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"];
//
// return Column(
// mainAxisSize: MainAxisSize.min,
// children: <Widget>[
// Text(
// "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}",
// style: Theme.of(context).textTheme.bodyText2.copyWith(
// fontSize: isSmallScreen(context) ? 12 : 14,
// ),
// textAlign: TextAlign.center,
// ),
// Text(
// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}",
// style: Theme.of(context).textTheme.bodyText2.copyWith(
// fontSize: isSmallScreen(context) ? 12 : 14,
// ),
// textAlign: TextAlign.center,
// ),
// ],
// );
// },
// )
// ],
// ),
// ),
// ),
// );
// }
// );
// }
// }
class DisplayTrainDestination extends StatelessWidget {
final TrainData trainData;
DisplayTrainDestination({required this.trainData});
@override
Widget build(BuildContext context) {
final destination = trainData.stations.last;
return Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: Text(
"Destinația",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 20 : 22,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0),
child: Text(
destination.name,
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 20,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
Builder(
builder: (context) {
final arrival = destination.arrival!.scheduleTime;
final delay = trainData.stations.last.arrival!.status?.delay ?? 0;
final parts = arrival.split(':');
final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1]));
final arrivalWithDelay = arrivalDT.add(Duration(minutes: delay));
final arrivalWithDelayString = '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}';
// const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"];
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// Text(
// "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}",
// style: Theme.of(context).textTheme.bodyText2?.copyWith(
// fontSize: isSmallScreen(context) ? 12 : 14,
// ),
// textAlign: TextAlign.center,
// ),
Text.rich(
// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}",
TextSpan(
text: 'la',
children: [
TextSpan(text: ' '),
TextSpan(
text: '$arrival',
style: delay == 0 ? null : TextStyle(
decoration: TextDecoration.lineThrough,
),
),
if (delay != 0) ...[
TextSpan(text: ' '),
TextSpan(
text: '$arrivalWithDelayString',
style: TextStyle(
color: delay > 0 ? Colors.red.shade300 : Colors.green.shade300,
),
),
]
],
),
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 14 : 16,
),
textAlign: TextAlign.center,
),
],
);
},
)
],
),
),
),
);
}
}
class DisplayTrainRouteDistance extends StatelessWidget {
final TrainData trainData;
DisplayTrainRouteDistance({required this.trainData});
@override
Widget build(BuildContext context) {
return Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"Distanța rutei",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 20 : 22,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
Text(
"${trainData.stations.last.km} km",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 20,
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}
// class DisplayTrainRouteDuration extends StatelessWidget {
// final TrainData trainData;
//
// DisplayTrainRouteDuration({required this.trainData});
//
// @override
// Widget build(BuildContext context) {
// return Card(
// child: Center(
// child: Padding(
// padding: const EdgeInsets.all(2),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: <Widget>[
// Text(
// "Durata rutei",
// style: Theme.of(context).textTheme.bodyText2?.copyWith(
// fontSize: isSmallScreen(context) ? 16 : 18,
// fontWeight: FontWeight.bold,
// ),
// textAlign: TextAlign.center,
// ),
// FutureDisplay<Duration>(
// future: trainData.routeDuration,
// builder: (context, duration) {
// var durationString = StringBuffer();
//
// bool firstWritten = false;
//
// if (duration.inDays > 0) {
// firstWritten = true;
// if (duration.inDays == 1) durationString.write("1 zi");
// else durationString.write("${duration.inDays} zile");
// duration -= Duration(days: duration.inDays);
// }
//
// if (duration.inHours > 0) {
// if (firstWritten) {
// durationString.write(", ");
// }
// firstWritten = true;
// if (duration.inHours == 1) durationString.write("1 oră");
// else durationString.write("${duration.inHours} ore");
// duration -= Duration(hours: duration.inHours);
// }
//
// if (duration.inMinutes > 0) {
// if (firstWritten) {
// durationString.write(", ");
// }
// firstWritten = true;
// if (duration.inMinutes == 1) durationString.write("1 minut");
// else durationString.write("${duration.inMinutes} minute");
// duration -= Duration(minutes: duration.inMinutes);
// }
//
// return Text(
// durationString.toString(),
// style: Theme.of(context).textTheme.bodyText2?.copyWith(
// fontSize: isSmallScreen(context) ? 14 : 16,
// ),
// textAlign: TextAlign.center,
// );
// },
// ),
// ],
// ),
// ),
// ),
// );
// }
// }
class DisplayTrainStations extends StatelessWidget {
final TrainData trainData;
DisplayTrainStations({required this.trainData});
@override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return IndexedSemantics(
child: DisplayTrainStation(
station: trainData.stations[index],
),
index: index,
);
},
childCount: trainData.stations.length,
addSemanticIndexes: true,
),
);
}
}

View file

@ -0,0 +1,476 @@
import 'package:flutter/material.dart';
import 'package:info_tren/models/train_data.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart' show isSmallScreen;
class DisplayTrainStation extends StatelessWidget {
final Station station;
DisplayTrainStation({required this.station});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Builder(
builder: (context) {
final delay = station.departure?.status?.delay ?? station.arrival?.status?.delay;
final real = station.departure?.status?.real ?? station.arrival?.status?.real;
final isDelayed = delay != null && delay > 0 && real == true;
final isOnTime = delay != null && delay <= 0 && real == true;
final isNotScheduled = false;
return KmBadge(
station: station,
isNotScheduled: isNotScheduled,
isDelayed: isDelayed,
isOnTime: isOnTime,
);
}
),
Expanded(
child: Title(
station: station,
),
),
],
),
Time(
station: station,
),
Delay(
station: station,
),
],
),
),
);
}
}
class KmBadge extends StatelessWidget {
final Station station;
final bool isNotScheduled;
final bool isOnTime;
final bool isDelayed;
KmBadge({
required this.station,
this.isNotScheduled = false,
this.isOnTime = false,
this.isDelayed = false,
});
@override
Widget build(BuildContext context) {
Color foregroundColor = Colors.white70;
Color? backgroundColor;
if (isNotScheduled) {
foregroundColor = Colors.orange.shade300;
backgroundColor = Colors.orange.shade900.withOpacity(0.3);
}
else if (isOnTime) {
foregroundColor = Colors.green.shade300;
backgroundColor = Colors.green.shade900.withOpacity(0.3);
}
else if (isDelayed) {
foregroundColor = Colors.red.shade300;
backgroundColor = Colors.red.shade900.withOpacity(0.3);
}
return Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 2,
color: foregroundColor,
),
color: backgroundColor,
),
width: isSmallScreen(context) ? 42 : 48,
height: isSmallScreen(context) ? 42 : 48,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Center(
child: Text(
station.km.toString(),
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 16 : 20,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor,
),
textAlign: TextAlign.center,
),
),
),
Text(
"km",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: 10,
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor,
),
),
],
),
),
);
}
}
class Title extends StatelessWidget {
final Station station;
Title({
required this.station
});
@override
Widget build(BuildContext context) {
return Text(
station.name,
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w500 : FontWeight.w300,
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal,
),
textAlign: TextAlign.center,
);
}
}
class Time extends StatelessWidget {
final Station station;
Time({
required this.station,
});
@override
Widget build(BuildContext context) {
if (station.arrival == null) {
// Plecare
return DepartureTime(
station: station,
firstStation: true,
);
}
if (station.departure == null) {
// Sosire
return ArrivalTime(
station: station,
finalStation: true,
);
}
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22,
),
),
Container(width: 2,),
ArrivalTime(station: station,),
Expanded(child: Container(),),
StopTime(station: station,),
Expanded(child: Container(),),
DepartureTime(station: station,),
Container(width: 2,),
Text(
"",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22,
),
),
],
);
}
}
class ArrivalTime extends StatelessWidget {
final Station station;
final bool finalStation;
ArrivalTime({
required this.station,
this.finalStation = false,
});
@override
Widget build(BuildContext context) {
if (station.arrival == null) {
return Container();
}
if (finalStation) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22,
),
),
Container(width: 2,),
Text("sosire la "),
ArrivalTime(station: station,),
Expanded(child: Container(),),
],
);
}
else {
final delay = station.arrival!.status?.delay ?? 0;
final time = station.arrival!.scheduleTime;
if (delay == 0) {
return Text("$time");
}
else if (delay > 0) {
final splits = time.split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final newDate = oldDate.add(Duration(minutes: delay));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
color: Colors.red.shade300,
),
),
],
);
}
else {
final splits = time.split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final newDate = oldDate.add(Duration(minutes: delay));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
color: Colors.green.shade300,
),
),
],
);
}
}
}
}
class StopTime extends StatelessWidget {
final Station station;
StopTime({
required this.station,
});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"staționează pentru",
textAlign: TextAlign.center,
),
Builder(
builder: (context) {
int stopsForInt = station.stoppingTime!;
if (stopsForInt == 1) {
return Text(
"1 minut",
textAlign: TextAlign.center,
);
}
else if (stopsForInt < 20) {
return Text(
"${station.stoppingTime} minute",
textAlign: TextAlign.center,
);
}
else {
return Text(
"${station.stoppingTime} de minute",
textAlign: TextAlign.center,
);
}
},
)
],
);
}
}
class DepartureTime extends StatelessWidget {
final Station station;
final bool firstStation;
DepartureTime({
required this.station,
this.firstStation = false,
});
@override
Widget build(BuildContext context) {
if (station.departure == null) {
return Container();
}
if (firstStation) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(child: Container(),),
Text("plecare la "),
DepartureTime(station: station,),
Container(width: 2,),
Text(
"",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
fontSize: 22,
),
),
],
);
}
else {
final delay = station.departure!.status?.delay ?? 0;
final time = station.departure!.scheduleTime;
if (delay == 0) {
return Text("$time");
}
else if (delay > 0) {
final splits = time.split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final newDate = oldDate.add(Duration(minutes: delay));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
color: Colors.red.shade300,
),
),
],
);
}
else {
final splits = time.split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final newDate = oldDate.add(Duration(minutes: delay));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
color: Colors.green.shade300,
),
),
],
);
}
}
}
}
class Delay extends StatelessWidget {
final Station station;
Delay({
required this.station,
});
@override
Widget build(BuildContext context) {
if (station.arrival?.status == null && station.departure?.status == null) {
return Container();
}
var delay = station.arrival?.status?.delay;
if (station.departure?.status?.real == true) {
delay = station.departure?.status?.delay;
}
if (delay == 0 || delay == null) return Container();
else if (delay > 0) {
return Text(
"$delay minute întârziere",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
color: Colors.red.shade300,
fontSize: 14,
fontStyle: FontStyle.italic,
),
);
}
else if (delay < 0) {
return Text(
"${-delay} minute mai devreme",
style: Theme.of(context).textTheme.bodyText2?.copyWith(
color: Colors.green.shade300,
fontSize: 14,
fontStyle: FontStyle.italic,
),
);
}
return Container();
}
}

View file

@ -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: <Widget>[
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,
);
}
}
}
// @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,
// );
// }
// }
// }

View file

@ -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: <Widget>[
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: <Widget>[
@ -188,62 +188,63 @@ class LastUpdate extends StatelessWidget {
children: <Widget>[
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: <Widget>[
Padding(
padding: const EdgeInsets.all(2.0),
child: Text("Următoarea oprire", style: Theme.of(context).textTheme.headline5,),
),
Row(
children: <Widget>[
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: <Widget>[
// Padding(
// padding: const EdgeInsets.all(2.0),
// child: Text("Următoarea oprire", style: Theme.of(context).textTheme.headline5,),
// ),
// Row(
// children: <Widget>[
// 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: <Widget>[
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,
// )
],
);
}

View file

@ -1,72 +0,0 @@
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:info_tren/models/train_data.dart';
import 'package:info_tren/train_info_page/train_info_cupertino.dart';
import 'package:info_tren/train_info_page/train_info_material.dart';
mixin TrainInfoMixin {
String title;
bool showTrainData;
TrainLookupResult lookupResult;
bool requestedData;
}
class TrainInfo extends StatelessWidget {
static String routeName = "/trainInfo/display";
final int trainNumber;
TrainInfo({@required this.trainNumber});
@override
Widget build(BuildContext context) {
return TrainDataWebViewAdapter(
builder: (context) {
if (Platform.isAndroid) {
return TrainInfoMaterial(trainNumber: trainNumber,);
}
else if (Platform.isIOS) {
return TrainInfoCupertino(trainNumber: trainNumber,);
}
return null;
},
);
}
}
typedef FutureDisplayCallback<T>(BuildContext context, T data);
class FutureDisplay<T> extends StatelessWidget {
final Future<T> future;
final FutureDisplayCallback<T> builder;
FutureDisplay({Key key, @required this.future, @required this.builder}): super(key: key);
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) return builder(context, snapshot.data);
if (snapshot.hasError) throw snapshot.error;
if (snapshot.connectionState == ConnectionState.done) return Container();
Widget loadingWidget;
if (Platform.isAndroid) {
loadingWidget = CircularProgressIndicator();
}
else if (Platform.isIOS) {
loadingWidget = CupertinoActivityIndicator();
}
return Center(
child: loadingWidget,
);
},
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,506 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:info_tren/models/train_data.dart';
import 'package:info_tren/train_info_page/train_info.dart';
import 'package:info_tren/train_info_page/train_info_constants.dart';
class DisplayTrainStation extends StatelessWidget {
final OnDemandStation station;
DisplayTrainStation({@required this.station});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
FutureDisplay(
future: Future.wait([
station.delay,
station.realOrEstimate,
station.observations,
]),
builder: (context, data) {
final isDelayed = (data[0] as int) > 0 && (data[1] as RealOrEstimate) == RealOrEstimate.real;
final isOnTime = (data[0] as int) <= 0 && (data[1] as RealOrEstimate) == RealOrEstimate.real;
final isNotScheduled = data[2] == "ONI";
return KmBadge(
station: station,
isNotScheduled: isNotScheduled,
isDelayed: isDelayed,
isOnTime: isOnTime,
);
}
),
Expanded(
child: Title(
station: station,
),
)
],
),
Time(
station: station,
),
Delay(
station: station,
),
],
);
}
}
class KmBadge extends StatelessWidget {
final OnDemandStation station;
final bool isNotScheduled;
final bool isOnTime;
final bool isDelayed;
KmBadge({
@required this.station,
this.isNotScheduled = false,
this.isOnTime = false,
this.isDelayed = false,
});
@override
Widget build(BuildContext context) {
Color foregroundColor = FOREGROUND_WHITE;
Color backgroundColor;
if (isNotScheduled) {
foregroundColor = Color.fromRGBO(225, 175, 30, 1);
backgroundColor = Color.fromRGBO(80, 40, 10, 1);
}
else if (isOnTime) {
foregroundColor = Color.fromRGBO(130, 175, 65, 1);
backgroundColor = Color.fromRGBO(40, 80, 10, 1);
}
else if (isDelayed) {
foregroundColor = Color.fromRGBO(225, 75, 30, 1);
backgroundColor = Color.fromRGBO(80, 20, 10, 1);
}
return Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 2,
color: foregroundColor,
),
color: backgroundColor,
// color: CupertinoColors.activeOrange,
),
width: 48,
height: 48,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Center(
child: FutureDisplay<int>(
future: station.km,
builder: (context, value) {
return Text(
value.toString(),
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 18,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor,
),
textAlign: TextAlign.center,
);
},
),
),
),
Text(
"km",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 10,
color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor,
),
),
],
),
),
);
}
}
class Title extends StatelessWidget {
final OnDemandStation station;
Title({
@required this.station
});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<String>>(
future: Future.wait([
station.stationName,
station.observations
]),
builder: (context, items) {
return Text(
items[0],
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal,
),
textAlign: TextAlign.center,
);
},
);
}
}
class Time extends StatelessWidget {
final OnDemandStation station;
Time({
@required this.station,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<String>>(
future: Future.wait([
station.arrivalTime,
station.stopsFor,
station.departureTime,
]),
builder: (context, items) {
if (items[0].isEmpty) {
// Plecare
return DepartureTime(
station: station,
firstStation: true,
);
}
if (items[2].isEmpty) {
// Sosire
return ArrivalTime(
station: station,
finalStation: true,
);
}
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
),
),
Container(width: 2,),
ArrivalTime(station: station,),
Expanded(child: Container(),),
StopTime(station: station,),
Expanded(child: Container(),),
DepartureTime(station: station,),
Container(width: 2,),
Text(
"",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
),
),
],
);
},
);
}
}
class ArrivalTime extends StatelessWidget {
final OnDemandStation station;
final bool finalStation;
ArrivalTime({
@required this.station,
this.finalStation = false,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<Object>>(
future: Future.wait([
station.arrivalTime,
station.delay,
]),
builder: (context, data) {
if (finalStation) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
),
),
Container(width: 2,),
Text("sosire la "),
ArrivalTime(station: station,),
Expanded(child: Container(),),
],
);
}
else {
if (data[1] == 0) {
return Text("${data[0]}");
}
else if (data[1] as int > 0) {
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final oldDate = newDate.subtract(Duration(minutes: data[1] as int));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${data[0]}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed,
),
),
],
);
}
else {
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final oldDate = newDate.subtract(Duration(minutes: data[1] as int));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${data[0]}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.activeGreen,
),
),
],
);
}
}
},
);
}
}
class StopTime extends StatelessWidget {
final OnDemandStation station;
StopTime({
@required this.station,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<String>(
future: station.stopsFor,
builder: (context, stopsFor) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"staționează pentru",
textAlign: TextAlign.center,
),
Builder(
builder: (context) {
int stopsForInt = int.parse(stopsFor);
if (stopsForInt == 1) {
return Text(
"1 minut",
textAlign: TextAlign.center,
);
}
else if (stopsForInt < 20) {
return Text(
"$stopsFor minute",
textAlign: TextAlign.center,
);
}
else {
return Text(
"$stopsFor de minute",
textAlign: TextAlign.center,
);
}
},
)
],
);
},
);
}
}
class DepartureTime extends StatelessWidget {
final OnDemandStation station;
final bool firstStation;
DepartureTime({
@required this.station,
this.firstStation = false,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<Object>>(
future: Future.wait([
station.departureTime,
station.delay,
]),
builder: (context, data) {
if (firstStation) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(child: Container(),),
Text("plecare la "),
DepartureTime(station: station,),
Container(width: 2,),
Text(
"",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22,
),
),
],
);
}
else {
if (data[1] == 0) {
return Text("${data[0]}");
}
else if (data[1] as int > 0) {
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final oldDate = newDate.subtract(Duration(minutes: data[1] as int));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${data[0]}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed,
),
),
],
);
}
else {
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final oldDate = newDate.subtract(Duration(minutes: data[1] as int));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${data[0]}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.activeGreen,
),
),
],
);
}
}
},
);
}
}
class Delay extends StatelessWidget {
final OnDemandStation station;
Delay({
@required this.station,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<int>(
future: station.delay,
builder: (context, delay) {
if (delay == 0) return Container();
else if (delay > 0) {
return Text(
"$delay minute întârziere",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed,
fontSize: 12,
fontStyle: FontStyle.italic,
),
);
}
else if (delay < 0) {
return Text(
"${-delay} minute mai devreme",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.activeGreen,
fontSize: 12,
fontStyle: FontStyle.italic,
),
);
}
return Container();
},
);
}
}

View file

@ -1,944 +0,0 @@
import 'package:info_tren/train_info_page/train_info_animation_helpers.dart';
import 'package:info_tren/train_info_page/train_info_material_DisplayTrainStation.dart';
import 'package:info_tren/utils/stream_list.dart';
import '../models/train_data.dart';
import './train_info.dart';
import 'package:flutter/material.dart';
class TrainInfoMaterial extends StatefulWidget {
final int trainNumber;
TrainInfoMaterial({@required this.trainNumber});
@override
_TrainInfoMaterialState createState() => _TrainInfoMaterialState();
}
class _TrainInfoMaterialState extends State<TrainInfoMaterial> with TrainInfoMixin {
@override
void initState() {
super.initState();
title = widget.trainNumber.toString();
showTrainData = false;
requestedData = false;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!requestedData) {
requestedData = true;
TrainDataWebViewAdapter.of(context).loadTrain(widget.trainNumber).then((value) {
setState(() {
lookupResult = value;
});
if (lookupResult == TrainLookupResult.NOT_FOUND) {
Future.delayed(Duration(seconds: 5), () {
Navigator.of(context).pop();
});
}
else if (lookupResult == TrainLookupResult.FOUND) {
Future.delayed(Duration(seconds: 1, milliseconds: 500), () {
setState(() {
showTrainData = true;
});
});
}
});
}
}
@override
Widget build(BuildContext context) {
if (!showTrainData) {
return _TrainInfoMaterialBefore(
title: title,
lookupResult: lookupResult,
);
}
else {
return _TrainDataMaterialAfter(
title: title,
);
}
}
}
class _TrainInfoMaterialBefore extends StatefulWidget {
final String title;
final TrainLookupResult lookupResult;
_TrainInfoMaterialBefore({@required this.title, @required this.lookupResult});
@override
_TrainInfoMaterialBeforeState createState() => _TrainInfoMaterialBeforeState();
}
class _TrainInfoMaterialBeforeState extends State<_TrainInfoMaterialBefore> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.title ?? ""),
),
body: SafeArea(
bottom: false,
child: StreamBuilder<ProgressReport>(
stream: TrainDataWebViewAdapter.of(context).progressStream,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Container();
case ConnectionState.waiting:
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
Text(
"Conectare...",
style: Theme.of(context).textTheme.headline6,
),
],
),
);
case ConnectionState.active:
break;
case ConnectionState.done:
Navigator.of(context).pop();
return Container();
}
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ProgressReportDisplayEntry(
key: ValueKey(1),
completed: 1 <= snapshot.data.current,
waitingText: "Se crează WebView",
completedText: "WebView a fost creat",
),
ProgressReportDisplayEntry(
key: ValueKey(2),
completed: 2 <= snapshot.data.current,
waitingText: "Se încarcă pagina Informatica Feroviară",
completedText: "Pagina Informatica Feroviară a fost încărcată",
),
ProgressReportDisplayEntry(
key: ValueKey(3),
completed: 3 <= snapshot.data.current,
waitingText: "Se încarcă informațiile despre tren",
completedText: "Informațiile despre tren au fost încărcate",
),
if (widget.lookupResult != null)
...[
Container(height: 20,),
SizedBox(
width: double.infinity,
child: AnimatedBackground(
animationDuration: Duration(milliseconds: 250),
initialColor: Theme.of(context).scaffoldBackgroundColor,
backgroundColor:
widget.lookupResult == TrainLookupResult.FOUND
? Colors.green
: Colors.red,
child: Center(
child: Row(
children: <Widget>[
Expanded(child: Container(),),
if (widget.lookupResult == TrainLookupResult.FOUND)
Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 0, 8),
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.greenAccent),
)
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
widget.lookupResult == TrainLookupResult.FOUND
? "Trenul a fost găsit"
: widget.lookupResult == TrainLookupResult.NOT_FOUND
? "Trenul nu a fost găsit"
: "A apărut o eroare în căutarea trenului",
style: Theme.of(context).textTheme.headline6,
),
),
Expanded(child: Container(),),
],
),
),
),
),
],
],
),
);
},
),
),
);
}
}
bool isSmallScreen(BuildContext context) => MediaQuery.of(context).size.height <= 425;
class _TrainDataMaterialAfter extends StatefulWidget {
final String title;
_TrainDataMaterialAfter({@required this.title});
@override
_TrainDataMaterialAfterState createState() => _TrainDataMaterialAfterState();
}
class _TrainDataMaterialAfterState extends State<_TrainDataMaterialAfter> {
@override
Widget build(BuildContext context) {
return FutureBuilder<OnDemandTrainData>(
future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () {
Navigator.of(context).pop();
}),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.title ?? ""),
),
body: SafeArea(
child: Center(
child: CircularProgressIndicator(),
),
),
);
}
return Scaffold(
appBar: isSmallScreen(context) ? null : AppBar(
centerTitle: true,
title: FutureBuilder<List<String>>(
future: Future.wait([
snapshot.data.rang,
snapshot.data.trainNumber
]),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("Informații despre ${snapshot.data[0]} ${snapshot.data[1]}");
}
else {
return Text(widget.title ?? "");
}
},
),
),
body: Column(
children: <Widget>[
if (isSmallScreen(context))
FutureBuilder<List<String>>(
future: Future.wait([
snapshot.data.rang,
snapshot.data.trainNumber,
]),
builder: (context, snapshot) {
var title = "INFO TREN";
if (snapshot.hasData) title = "INFO TREN ─ ${snapshot.data[0]} ${snapshot.data[1]}";
return SlimAppBar(
title: title,
);
}
),
Expanded(
child: SafeArea(
bottom: false,
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: DisplayTrainID(trainData: snapshot.data,),
),
SliverToBoxAdapter(
child: DisplayTrainOperator(trainData: snapshot.data,),
),
SliverPadding(
padding: const EdgeInsets.only(left: 2, right: 2),
sliver: SliverToBoxAdapter(
child: DisplayTrainRoute(trainData: snapshot.data,),
),
),
SliverToBoxAdapter(
child: DisplayTrainDeparture(trainData: snapshot.data,),
),
SliverToBoxAdapter(
child: Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
),
SliverToBoxAdapter(
child: DisplayTrainLastInfo(trainData: snapshot.data,),
),
SliverToBoxAdapter(
child: IntrinsicHeight(
child: Row(
children: <Widget>[
Expanded(child: DisplayTrainNextStop(trainData: snapshot.data,)),
Expanded(child: DisplayTrainDestination(trainData: snapshot.data,)),
],
),
),
),
SliverToBoxAdapter(
child: IntrinsicHeight(
child: Row(
children: <Widget>[
Expanded(child: DisplayTrainRouteDuration(trainData: snapshot.data,)),
Expanded(child: DisplayTrainRouteDistance(trainData: snapshot.data,)),
],
),
),
),
SliverToBoxAdapter(
child: Divider(
color: Colors.white70,
height: isSmallScreen(context) ? 8 : 16,
),
),
DisplayTrainStations(
trainData: snapshot.data,
pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture,
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
),
),
),
],
),
);
},
);
}
}
class DisplayTrainID extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainID({@required this.trainData});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<String>>(
future: Future.wait([
trainData.rang,
trainData.trainNumber,
]),
builder: (context, list) {
return Text(
"${list[0]} ${list[1]}",
style: (isSmallScreen(context)
? Theme.of(context).textTheme.headline4
: Theme.of(context).textTheme.headline3).copyWith(
color: Theme.of(context).textTheme.bodyText2.color,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
);
},
);
}
}
class DisplayTrainOperator extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainOperator({@required this.trainData});
@override
Widget build(BuildContext context) {
return FutureDisplay<String>(
future: trainData.operator,
builder: (context, op) {
return Text(
op,
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontStyle: FontStyle.italic,
fontSize: isSmallScreen(context) ? 12 : 14,
),
textAlign: TextAlign.center,
);
},
);
}
}
class DisplayTrainRoute extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainRoute({@required this.trainData});
@override
Widget build(BuildContext context) {
return FutureDisplay(
future: Future.wait([trainData.route.from, trainData.route.to]),
builder: (context, routePieces) {
return Row(
children: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
routePieces[0],
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 16,
),
),
),
),
Expanded(child: Container(),),
Center(child: Text("-")),
Expanded(child: Container(),),
Center(
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
routePieces[1],
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 16,
),
textAlign: TextAlign.right,
),
),
),
],
);
},
);
}
}
class DisplayTrainDeparture extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainDeparture({@required this.trainData});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(2),
child: FutureDisplay<DateTime>(
future: trainData.departureDate,
builder: (context, dataPlecare) {
return Text(
"Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w200,
fontSize: isSmallScreen(context) ? 12 : 14,
),
textAlign: TextAlign.center,
);
},
),
);
}
}
class DisplayTrainLastInfo extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainLastInfo({@required this.trainData});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Text(
"Ultima informație",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 18 : 20,
fontWeight: FontWeight.bold,
),
),
),
),
Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: FutureDisplay(
future: trainData.lastInfo.station,
builder: (context, station) {
return Text(
station,
style: Theme.of(context).textTheme.bodyText2,
textAlign: TextAlign.left,
);
},
),
),
Expanded(child: Container(),),
Padding(
padding: const EdgeInsets.all(4),
child: FutureDisplay(
future: trainData.lastInfo.event,
builder: (context, event) {
return Text(
event,
style: Theme.of(context).textTheme.bodyText2,
textAlign: TextAlign.right,
);
},
),
),
],
),
Padding(
padding: const EdgeInsets.all(2),
child: Row(
children: <Widget>[
FutureDisplay<DateTime>(
future: trainData.lastInfo.dateAndTime,
builder: (context, dt) {
return Text(
"Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}",
textAlign: TextAlign.center,
);
},
),
Expanded(child: Container(),),
FutureBuilder(
initialData: 0,
future: trainData.lastInfo.delay,
builder: (context, snapshot) {
if (snapshot.data == 0) {
return Container();
}
if (snapshot.data > 0) {
return Text(
"${snapshot.data} minute întârziere",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 14,
color: Color.fromRGBO(200, 30, 15, 1),
),
);
}
else {
return Text(
"${-snapshot.data} minute mai devreme",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 12,
color: Color.fromRGBO(15, 200, 15, 1),
),
);
}
},
),
],
),
),
],
),
),
);
}
}
class DisplayTrainNextStop extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainNextStop({@required this.trainData});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: trainData.nextStop.stationName,
builder: (context, snapshot) {
if (!snapshot.hasData) return Container(height: 0,);
return Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: Text(
"Următoarea oprire",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 18 : 20,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
FutureDisplay(
future: trainData.nextStop.stationName,
builder: (context, station) {
return Padding(
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0),
child: Text(
station,
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 16 : 18,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
);
},
),
FutureDisplay<DateTime>(
future: trainData.nextStop.arrival,
builder: (context, arrival) {
const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"];
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 12 : 14,
),
textAlign: TextAlign.center,
),
Text(
"la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 12 : 14,
),
textAlign: TextAlign.center,
),
],
);
},
)
],
),
),
),
);
}
);
}
}
class DisplayTrainDestination extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainDestination({@required this.trainData});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: trainData.destination.stationName,
builder: (context, snapshot) {
if (!snapshot.hasData) return Container(height: 0,);
return Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: Text(
"Destinația",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 18 : 20,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
FutureDisplay(
future: trainData.destination.stationName,
builder: (context, station) {
return Padding(
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0),
child: Text(
station,
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 16 : 18,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
);
},
),
FutureDisplay<DateTime>(
future: trainData.destination.arrival,
builder: (context, arrival) {
const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"];
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 12 : 14,
),
textAlign: TextAlign.center,
),
Text(
"la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 12 : 14,
),
textAlign: TextAlign.center,
),
],
);
},
)
],
),
),
),
);
}
);
}
}
class DisplayTrainRouteDistance extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainRouteDistance({@required this.trainData});
@override
Widget build(BuildContext context) {
return Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"Distanța rutei",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 16 : 18,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
FutureDisplay(
future: trainData.routeDistance,
builder: (context, distance) {
return Text(
"$distance km",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 14 : 16,
),
textAlign: TextAlign.center,
);
},
),
],
),
),
),
);
}
}
class DisplayTrainRouteDuration extends StatelessWidget {
final OnDemandTrainData trainData;
DisplayTrainRouteDuration({@required this.trainData});
@override
Widget build(BuildContext context) {
return Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"Durata rutei",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 16 : 18,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
FutureDisplay<Duration>(
future: trainData.routeDuration,
builder: (context, duration) {
var durationString = StringBuffer();
bool firstWritten = false;
if (duration.inDays > 0) {
firstWritten = true;
if (duration.inDays == 1) durationString.write("1 zi");
else durationString.write("${duration.inDays} zile");
duration -= Duration(days: duration.inDays);
}
if (duration.inHours > 0) {
if (firstWritten) {
durationString.write(", ");
}
firstWritten = true;
if (duration.inHours == 1) durationString.write("1 oră");
else durationString.write("${duration.inHours} ore");
duration -= Duration(hours: duration.inHours);
}
if (duration.inMinutes > 0) {
if (firstWritten) {
durationString.write(", ");
}
firstWritten = true;
if (duration.inMinutes == 1) durationString.write("1 minut");
else durationString.write("${duration.inMinutes} minute");
duration -= Duration(minutes: duration.inMinutes);
}
return Text(
durationString.toString(),
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 14 : 16,
),
textAlign: TextAlign.center,
);
},
),
],
),
),
),
);
}
}
class DisplayTrainStations extends StatelessWidget {
final OnDemandTrainData trainData;
final Future pageLoadFuture;
DisplayTrainStations({@required this.trainData, @required this.pageLoadFuture});
@override
Widget build(BuildContext context) {
return StreamBuilder<List<OnDemandStation>>(
stream: listifyStream(trainData.stations(pageLoadFuture: pageLoadFuture)),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SliverToBoxAdapter(
child: Container(),
);
}
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return IndexedSemantics(
child: DisplayTrainStation(
station: snapshot.data[index],
),
index: index,
);
},
childCount: snapshot.data.length,
addSemanticIndexes: true,
),
);
},
);
}
}
class SlimAppBar extends StatelessWidget {
final String title;
final double size;
// final Function onBackTap;
SlimAppBar({
@required this.title,
this.size = 24,
// this.onBackTap,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
height: size,
child: Container(
color:
Theme.of(context).appBarTheme?.color ??
Theme.of(context).primaryColor,
child: InkWell(
onTap: (ModalRoute.of(context)?.canPop ?? false)
? () => Navigator.of(context).pop()
: null,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
height: size,
width: size,
child: (ModalRoute.of(context)?.canPop ?? false)
? BackButtonIcon()
: null,
),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.all(2),
child: Text(
title,
textAlign: TextAlign.center,
style:
Theme.of(context).appBarTheme.textTheme?.caption?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.bodyText2?.color) ??
Theme.of(context).textTheme.caption.copyWith(color: Theme.of(context).textTheme.bodyText2.color),
),
),
),
),
Container(
height: size,
width: size,
),
],
),
),
),
);
}
}

View file

@ -1,509 +0,0 @@
import 'package:flutter/material.dart';
import 'package:info_tren/models/train_data.dart';
import 'package:info_tren/train_info_page/train_info.dart';
import 'package:info_tren/train_info_page/train_info_material.dart' show isSmallScreen;
class DisplayTrainStation extends StatelessWidget {
final OnDemandStation station;
DisplayTrainStation({@required this.station});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
FutureDisplay(
future: Future.wait([
station.delay,
station.realOrEstimate,
station.observations,
]),
builder: (context, data) {
final isDelayed = (data[0] as int) > 0 && (data[1] as RealOrEstimate) == RealOrEstimate.real;
final isOnTime = (data[0] as int) <= 0 && (data[1] as RealOrEstimate) == RealOrEstimate.real;
final isNotScheduled = data[2] == "ONI";
return KmBadge(
station: station,
isNotScheduled: isNotScheduled,
isDelayed: isDelayed,
isOnTime: isOnTime,
);
}
),
Expanded(
child: Title(
station: station,
),
),
],
),
Time(
station: station,
),
Delay(
station: station,
),
],
),
),
);
}
}
class KmBadge extends StatelessWidget {
final OnDemandStation station;
final bool isNotScheduled;
final bool isOnTime;
final bool isDelayed;
KmBadge({
@required this.station,
this.isNotScheduled = false,
this.isOnTime = false,
this.isDelayed = false,
});
@override
Widget build(BuildContext context) {
Color foregroundColor = Colors.white70;
Color backgroundColor;
if (isNotScheduled) {
foregroundColor = Colors.orange.shade300;
backgroundColor = Colors.orange.shade900.withOpacity(0.3);
}
else if (isOnTime) {
foregroundColor = Colors.green.shade300;
backgroundColor = Colors.green.shade900.withOpacity(0.3);
}
else if (isDelayed) {
foregroundColor = Colors.red.shade300;
backgroundColor = Colors.red.shade900.withOpacity(0.3);
}
return Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 2,
color: foregroundColor,
),
color: backgroundColor,
),
width: isSmallScreen(context) ? 42 : 48,
height: isSmallScreen(context) ? 42 : 48,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Center(
child: FutureDisplay<int>(
future: station.km,
builder: (context, value) {
return Text(
value.toString(),
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 14 : 18,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor,
),
textAlign: TextAlign.center,
);
},
),
),
),
Text(
"km",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 10,
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor,
),
),
],
),
),
);
}
}
class Title extends StatelessWidget {
final OnDemandStation station;
Title({
@required this.station
});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<String>>(
future: Future.wait([
station.stationName,
station.observations
]),
builder: (context, items) {
return Text(
items[0],
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal,
),
textAlign: TextAlign.center,
);
},
);
}
}
class Time extends StatelessWidget {
final OnDemandStation station;
Time({
@required this.station,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<String>>(
future: Future.wait([
station.arrivalTime,
station.stopsFor,
station.departureTime,
]),
builder: (context, items) {
if (items[0].isEmpty) {
// Plecare
return DepartureTime(
station: station,
firstStation: true,
);
}
if (items[2].isEmpty) {
// Sosire
return ArrivalTime(
station: station,
finalStation: true,
);
}
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22,
),
),
Container(width: 2,),
ArrivalTime(station: station,),
Expanded(child: Container(),),
StopTime(station: station,),
Expanded(child: Container(),),
DepartureTime(station: station,),
Container(width: 2,),
Text(
"",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22,
),
),
],
);
},
);
}
}
class ArrivalTime extends StatelessWidget {
final OnDemandStation station;
final bool finalStation;
ArrivalTime({
@required this.station,
this.finalStation = false,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<Object>>(
future: Future.wait([
station.arrivalTime,
station.delay,
]),
builder: (context, data) {
if (finalStation) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22,
),
),
Container(width: 2,),
Text("sosire la "),
ArrivalTime(station: station,),
Expanded(child: Container(),),
],
);
}
else {
if (data[1] == 0) {
return Text("${data[0]}");
}
else if (data[1] as int > 0) {
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final oldDate = newDate.subtract(Duration(minutes: data[1] as int));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${data[0]}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Colors.red.shade300,
),
),
],
);
}
else {
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final oldDate = newDate.subtract(Duration(minutes: data[1] as int));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${data[0]}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Colors.green.shade300,
),
),
],
);
}
}
},
);
}
}
class StopTime extends StatelessWidget {
final OnDemandStation station;
StopTime({
@required this.station,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<String>(
future: station.stopsFor,
builder: (context, stopsFor) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"staționează pentru",
textAlign: TextAlign.center,
),
Builder(
builder: (context) {
int stopsForInt = int.parse(stopsFor);
if (stopsForInt == 1) {
return Text(
"1 minut",
textAlign: TextAlign.center,
);
}
else if (stopsForInt < 20) {
return Text(
"$stopsFor minute",
textAlign: TextAlign.center,
);
}
else {
return Text(
"$stopsFor de minute",
textAlign: TextAlign.center,
);
}
},
)
],
);
},
);
}
}
class DepartureTime extends StatelessWidget {
final OnDemandStation station;
final bool firstStation;
DepartureTime({
@required this.station,
this.firstStation = false,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<List<Object>>(
future: Future.wait([
station.departureTime,
station.delay,
]),
builder: (context, data) {
if (firstStation) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(child: Container(),),
Text("plecare la "),
DepartureTime(station: station,),
Container(width: 2,),
Text(
"",
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 22,
),
),
],
);
}
else {
if (data[1] == 0) {
return Text("${data[0]}");
}
else if (data[1] as int > 0) {
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final oldDate = newDate.subtract(Duration(minutes: data[1] as int));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${data[0]}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Colors.red.shade300,
),
),
],
);
}
else {
final splits = (data[0] as String).split(":").map((s) => int.parse(s)).toList();
final now = DateTime.now();
final newDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]);
final oldDate = newDate.subtract(Duration(minutes: data[1] as int));
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
decoration: TextDecoration.lineThrough,
),
),
Text(
"${data[0]}",
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Colors.green.shade300,
),
),
],
);
}
}
},
);
}
}
class Delay extends StatelessWidget {
final OnDemandStation station;
Delay({
@required this.station,
});
@override
Widget build(BuildContext context) {
return FutureDisplay<int>(
future: station.delay,
builder: (context, delay) {
if (delay == 0) return Container();
else if (delay > 0) {
return Text(
"$delay minute întârziere",
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Colors.red.shade300,
fontSize: 12,
fontStyle: FontStyle.italic,
),
);
}
else if (delay < 0) {
return Text(
"${-delay} minute mai devreme",
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Colors.green.shade300,
fontSize: 12,
fontStyle: FontStyle.italic,
),
);
}
return Container();
},
);
}
}

View file

@ -1,385 +0,0 @@
import 'dart:convert';
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:info_tren/train_info_page/train_info.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:tuple/tuple.dart';
part 'train_info_prompt.g.dart';
typedef TrainSelectedCallback(int trainNumber);
mixin TrainInfoPromptCommon {
static String routeName = "/trainInfo/chooseTrain";
onTrainSelected(BuildContext context, int selection) {
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: selection);
}
}
mixin TrainInfoPromptListHandling {
List<TrainOperatorLines> operators = [];
Future loadOperators(BuildContext context) async {
operators = [];
final operatorsString = await DefaultAssetBundle.of(context).loadString("assets/lines/files.txt");
final operatorsFilesList = operatorsString.split("\n");
final decoder = JsonDecoder();
for (final operatorFile in operatorsFilesList) {
final operatorString = await DefaultAssetBundle.of(context).loadString("assets/lines/$operatorFile");
final operatorData = decoder.convert(operatorString);
final _operator = TrainOperatorLines.fromJson(operatorData);
operators.add(_operator);
}
}
Widget getOperatorsListView(BuildContext context, {String currentInput = "", @required TrainSelectedCallback onTrainSelected}) {
var sliversTuple = operators.map(
(op) => Tuple2(
getFilteredLines(op, currentInput),
getOperatorSliver(context, op, currentInput, onTrainSelected)
)
)
.where((tuple) => tuple.item1.isNotEmpty).toList();
if (currentInput.isNotEmpty) sliversTuple.sort((a, b) {
final aTrain = a.item1.first;
final bTrain = b.item1.first;
final inputAsRegExp = RegExp(currentInput);
final matchOnA = inputAsRegExp.firstMatch(aTrain.number);
final matchOnB = inputAsRegExp.firstMatch(bTrain.number);
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start;
if (aTrain.number.length != bTrain.number.length) return aTrain.number.length - bTrain.number.length;
return aTrain.number.compareTo(bTrain.number);
});
var slivers = sliversTuple.map((tuple) => tuple.item2).toList();
return CustomScrollView(
slivers: <Widget>[
...slivers,
SliverToBoxAdapter(
child: getUseCurrentInputWidget(currentInput, onTrainSelected),
),
SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
);
}
Widget getUseCurrentInputWidget(String currentInput, TrainSelectedCallback onTrainSelected) {
if (currentInput.isEmpty) {
return Container();
}
if (int.tryParse(currentInput) == null) {
return Container();
}
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (Platform.isAndroid)
ListTile(
title: Text("Caută trenul cu numărul $currentInput"),
onTap: () {
onTrainSelected(int.parse(currentInput));
},
)
else if (Platform.isIOS)
GestureDetector(
onTap: () {
onTrainSelected(int.parse(currentInput));
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Caută trenul cu numărul $currentInput")
],
)
),
),
Divider(),
],
);
}
List<_TrainOperatorTrainDescription> getFilteredLines(TrainOperatorLines _operator, String currentInput) {
if (currentInput.isNotEmpty) {
final filteredLines = _operator.trains
.where((elem) => elem.number.contains(currentInput))
.toList();
filteredLines.sort((a, b) {
final inputAsRegExp = RegExp(currentInput);
final matchOnA = inputAsRegExp.firstMatch(a.number);
final matchOnB = inputAsRegExp.firstMatch(b.number);
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start;
if (a.number.length != b.number.length) return a.number.length - b.number.length;
return a.number.compareTo(b.number);
});
return filteredLines;
}
else {
return _operator.trains;
}
}
Widget getOperatorSliver(BuildContext context, TrainOperatorLines _operator, String currentInput, TrainSelectedCallback onTrainSelected) {
final filteredLines = getFilteredLines(_operator, currentInput);
if (filteredLines.isEmpty) {
return SliverToBoxAdapter(child: Container(),);
}
return SliverPrototypeExtentList(
prototypeItem: Column(
children: <Widget>[
getLineListItem(
context,
op: TrainOperatorLines(),
line: _TrainOperatorTrainDescription()
),
Divider(),
],
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Column(
children: <Widget>[
getLineListItem(
context,
op: _operator,
line: filteredLines[index],
onTrainSelected: onTrainSelected
),
Divider(),
],
);
},
childCount: filteredLines.length,
addSemanticIndexes: true,
),
);
}
Widget getLineListItem(BuildContext context, {TrainOperatorLines op, _TrainOperatorTrainDescription line, TrainSelectedCallback onTrainSelected}) {
if (Platform.isAndroid) {
return ListTile(
dense: true,
title: Text("${line.rang ?? ""} ${line.number ?? ""}"),
subtitle: Text(op.operator ?? ""),
onTap: () {
onTrainSelected(line.internalNumber);
},
);
}
else if (Platform.isIOS) {
return GestureDetector(
onTap: () {
onTrainSelected(line.internalNumber);
},
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
child: SizedBox(
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
op.operator ?? "",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200),
textAlign: TextAlign.left,
),
Text(
"${line.rang ?? ""} ${line.number ?? ""}",
textAlign: TextAlign.left,
),
],
),
),
),
);
}
return null;
}
}
class TrainInfoPromptMaterial extends StatefulWidget {
@override
_TrainInfoPromptMaterialState createState() => _TrainInfoPromptMaterialState();
}
class _TrainInfoPromptMaterialState extends State<TrainInfoPromptMaterial> with TrainInfoPromptCommon, TrainInfoPromptListHandling {
TextEditingController trainNoController = TextEditingController();
@override
void initState() {
super.initState();
loadOperators(context).then((_) {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Informații despre tren"),
centerTitle: true,
),
body: SafeArea(
bottom: false,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: TextField(
controller: trainNoController,
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Numărul trenului",
),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
textInputAction: TextInputAction.search,
keyboardType: TextInputType.number,
onChanged: (_) {
setState(() {});
},
),
),
Expanded(
child: getOperatorsListView(context, currentInput: trainNoController.text, onTrainSelected: (number) {
onTrainSelected(context, number);
})
)
],
),
),
);
}
}
class TrainInfoPromptCupertino extends StatefulWidget {
@override
_TrainInfoPromptCupertinoState createState() => _TrainInfoPromptCupertinoState();
}
class _TrainInfoPromptCupertinoState extends State<TrainInfoPromptCupertino> with TrainInfoPromptCommon, TrainInfoPromptListHandling {
TextEditingController trainNoController = TextEditingController();
@override
void initState() {
super.initState();
loadOperators(context).then((_) {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Informații despre tren"),
),
child: SafeArea(
bottom: false,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4),
child: CupertinoTextField(
controller: trainNoController,
autofocus: true,
placeholder: "Numărul trenului",
textInputAction: TextInputAction.search,
keyboardType: TextInputType.number,
onChanged: (_) {
setState(() {});
},
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
),
),
Expanded(
child: getOperatorsListView(
context,
currentInput: trainNoController.text, onTrainSelected: (number) {
onTrainSelected(context, number);
}
)
)
],
),
),
);
}
}
@JsonSerializable()
class TrainOperatorLines {
@JsonKey(name: "short_name")
final String shortName;
final String operator;
@JsonKey(name: "versiune")
final String version;
@JsonKey(name: "trenuri")
final List<_TrainOperatorTrainDescription> trains;
TrainOperatorLines({
this.operator,
this.shortName = "",
this.version,
this.trains,
});
factory TrainOperatorLines.fromJson(Map<String, dynamic> json) => _$TrainOperatorLinesFromJson(json);
Map<String, dynamic> toJson() => _$TrainOperatorLinesToJson(this);
}
@JsonSerializable()
class _TrainOperatorTrainDescription {
final String rang;
@JsonKey(name: "numar")
final String number;
@JsonKey(name: "numar_intern")
final int internalNumber;
_TrainOperatorTrainDescription({
this.number,
this.rang,
this.internalNumber
});
factory _TrainOperatorTrainDescription.fromJson(Map<String, dynamic> json) => _$_TrainOperatorTrainDescriptionFromJson(json);
Map<String, dynamic> toJson() => _$_TrainOperatorTrainDescriptionToJson(this);
}

View file

@ -1,44 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'train_info_prompt.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
TrainOperatorLines _$TrainOperatorLinesFromJson(Map<String, dynamic> json) {
return TrainOperatorLines(
operator: json['operator'] as String,
shortName: json['short_name'] as String,
version: json['versiune'] as String,
trains: (json['trenuri'] as List)
?.map((e) => e == null
? null
: _TrainOperatorTrainDescription.fromJson(
e as Map<String, dynamic>))
?.toList());
}
Map<String, dynamic> _$TrainOperatorLinesToJson(TrainOperatorLines instance) =>
<String, dynamic>{
'short_name': instance.shortName,
'operator': instance.operator,
'versiune': instance.version,
'trenuri': instance.trains
};
_TrainOperatorTrainDescription _$_TrainOperatorTrainDescriptionFromJson(
Map<String, dynamic> json) {
return _TrainOperatorTrainDescription(
number: json['numar'] as String,
rang: json['rang'] as String,
internalNumber: json['numar_intern'] as int);
}
Map<String, dynamic> _$_TrainOperatorTrainDescriptionToJson(
_TrainOperatorTrainDescription instance) =>
<String, dynamic>{
'rang': instance.rang,
'numar': instance.number,
'numar_intern': instance.internalNumber
};

View file

@ -0,0 +1,12 @@
import 'dart:io';
import 'package:info_tren/models/ui_design.dart';
UiDesign get defaultUiDesign {
if (Platform.isIOS) {
return UiDesign.CUPERTINO;
}
else {
return UiDesign.MATERIAL;
}
}

View file

@ -0,0 +1,12 @@
import 'package:info_tren/models/train_data.dart';
String stateToString(State state) {
switch(state) {
case State.PASSING:
return 'trecere fără oprire';
case State.ARRIVAL:
return 'sosire';
case State.DEPARTURE:
return 'plecare';
}
}

12
lib/utils/string.dart Normal file
View file

@ -0,0 +1,12 @@
extension TakeWhile on String {
String takeWhile(Function charValidator) {
StringBuffer output = StringBuffer();
for (final char in this.codeUnits) {
if (charValidator(char)) output.writeCharCode(char);
else break;
}
return output.toString();
}
}

View file

@ -1,34 +0,0 @@
import 'dart:convert';
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:webview_flutter/webview_flutter.dart';
/// Evaluates a JavaScript function on the given WebView.
///
/// The JavaScript function must return a String.
///
/// On Android, the `String` resulted from the evaluation
/// is JSON parsed. On iOS, the `String` is returned as is.
///
/// Other platforms are not supported. The returned value
/// in this case will be `null`.
Future<String> wInvoke({
@required WebViewController webViewController,
@required String jsFunctionContent,
bool isFunctionAlready = false
}) async {
final actualJS = isFunctionAlready ?
jsFunctionContent :
"""
(() => {
$jsFunctionContent
})()
""";
final res = await webViewController.evaluateJavascript(actualJS);
if (Platform.isAndroid) return JsonDecoder().convert(res) as String;
else if (Platform.isIOS) return res;
else return null;
}

View file

@ -1,90 +1,90 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "24.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "0.36.4"
version: "2.1.0"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.2"
version: "2.2.0"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.8.1"
build:
dependency: transitive
description:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.5"
version: "2.1.0"
build_config:
dependency: transitive
description:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
version: "1.0.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "3.0.0"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.6"
version: "2.0.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.1"
version: "2.1.1"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.6"
version: "7.1.0"
built_collection:
dependency: transitive
description:
name: built_collection
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.2"
version: "5.1.0"
built_value:
dependency: transitive
description:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "6.7.0"
version: "8.1.2"
characters:
dependency: transitive
description:
@ -98,21 +98,28 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
clock:
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
name: clock
name: checked_yaml
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "2.0.1"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.3"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.0"
version: "4.1.0"
collection:
dependency: transitive
description:
@ -126,21 +133,14 @@ packages:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "3.0.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
csslib:
dependency: transitive
description:
name: csslib
url: "https://pub.dartlang.org"
source: hosted
version: "0.16.1"
version: "3.0.1"
cupertino_icons:
dependency: "direct main"
description:
@ -154,122 +154,110 @@ packages:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.9"
fake_async:
version: "2.0.3"
file:
dependency: transitive
description:
name: fake_async
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "6.1.2"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.9"
version: "1.0.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
front_end:
dependency: transitive
flutter_redux:
dependency: "direct main"
description:
name: front_end
name: flutter_redux
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.19"
version: "0.8.2"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.7"
version: "2.0.1"
graphs:
dependency: transitive
description:
name: graphs
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
html:
dependency: transitive
description:
name: html
url: "https://pub.dartlang.org"
source: hosted
version: "0.14.0+2"
version: "2.0.0"
http:
dependency: "direct main"
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.0+2"
version: "0.13.3"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "3.0.1"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.3"
version: "4.0.0"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.3"
version: "1.0.3"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1+1"
version: "0.6.3"
json_annotation:
dependency: "direct main"
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
version: "4.1.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
kernel:
dependency: transitive
description:
name: kernel
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.19"
version: "5.0.0"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "0.11.3+2"
version: "1.0.1"
matcher:
dependency: transitive
description:
@ -283,28 +271,21 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.6+3"
version: "1.0.0"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
package_resolver:
dependency: transitive
description:
name: package_resolver
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.10"
version: "2.0.0"
path:
dependency: transitive
description:
@ -318,35 +299,42 @@ packages:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.11.1"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
version: "1.5.0"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.2"
version: "2.0.0"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
version: "1.0.0"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
version: "3.0.1"
redux:
dependency: transitive
description:
name: redux
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
rxdart:
dependency: "direct main"
description:
@ -360,14 +348,14 @@ packages:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.5"
version: "1.2.0"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.3"
version: "1.0.1"
sky_engine:
dependency: transitive
description: flutter
@ -379,7 +367,14 @@ packages:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.4+2"
version: "1.1.0"
source_helper:
dependency: transitive
description:
name: source_helper
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
source_span:
dependency: transitive
description:
@ -407,7 +402,7 @@ packages:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.19"
version: "2.0.0"
string_scanner:
dependency: transitive
description:
@ -422,27 +417,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
timing:
dependency: transitive
description:
name: timing
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.1+1"
version: "1.0.0"
tuple:
dependency: "direct main"
description:
name: tuple
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "2.0.0"
typed_data:
dependency: transitive
description:
@ -463,28 +451,20 @@ packages:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+12"
version: "1.0.0"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.14"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.11+2"
version: "2.1.0"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.16"
version: "3.1.0"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=1.5.0"

View file

@ -1,5 +1,5 @@
name: info_tren
description: O aplicație de vizualizare a datelor puse la dispoziție de Informatica Feroviară.xe
description: O aplicație de vizualizare a datelor puse la dispoziție de Informatica Feroviară.
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
@ -11,10 +11,10 @@ description: O aplicație de vizualizare a datelor puse la dispoziție de Inform
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 2.0.6
version: 2.0.7
environment:
sdk: ">=2.3.0 <3.0.0"
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
@ -23,18 +23,17 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
# cupertino_icons: ^0.1.2
json_annotation: ^2.0.0
rxdart: ^0.22.0
http: ^0.12.0
webview_flutter: ^0.3.0
http: ^0.13.0
cupertino_icons: ^0.1.2
tuple: ^1.0.2
tuple: ^2.0.0
flutter_redux: ^0.8.2
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.0.0
json_serializable: ^3.0.0
# flutter_test:
# sdk: flutter
build_runner: ^2.1.0
json_serializable: ^5.0.0
# For information on the generic Dart part of this file, see the
@ -64,7 +63,17 @@ flutter:
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
fonts:
- family: Atkinson Hyperlegible
fonts:
- asset: fonts/ah/ah-Regular.ttf
- asset: fonts/ah/ah-Italic.ttf
style: italic
- asset: fonts/ah/ah-Bold.ttf
weight: 700
- asset: fonts/ah/ah-BoldItalic.ttf
weight: 700
style: italic
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf

BIN
web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

BIN
web/icons/Icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
web/icons/Icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

101
web/index.html Normal file
View file

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="info_tren">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<title>info_tren</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
</html>

35
web/manifest.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "info_tren",
"short_name": "info_tren",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}