diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT index 3d1aef6..37dfd83 100644 --- a/CHANGELOG.TXT +++ b/CHANGELOG.TXT @@ -1,3 +1,8 @@ +v2.0.7 +Switched from WebView to API +Updated app to latest Flutter +Tweaks + v2.0.6 Brought feature parity with iOS _(except for v2.0.2, which is iOS specific)_. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle index 3f9921f..e8883bd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,24 +26,28 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 30 - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' } - lintOptions { - disable 'InvalidPackage' + sourceSets { + main.java.srcDirs += 'src/main/kotlin' } 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' } diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index ad28c11..e798ce8 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="xyz.dcdevelop.info_tren"> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1b418e2..de387cc 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,38 +1,40 @@ - - - - - + + - - + android:windowSoftInputMode="adjustResize"> + - + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> + + + diff --git a/android/app/src/main/kotlin/ml/dandevelop/info_tren/MainActivity.kt b/android/app/src/main/kotlin/xyz/dcdevelop/info_tren/MainActivity.kt similarity index 75% rename from android/app/src/main/kotlin/ml/dandevelop/info_tren/MainActivity.kt rename to android/app/src/main/kotlin/xyz/dcdevelop/info_tren/MainActivity.kt index 5939f33..0fd345f 100644 --- a/android/app/src/main/kotlin/ml/dandevelop/info_tren/MainActivity.kt +++ b/android/app/src/main/kotlin/xyz/dcdevelop/info_tren/MainActivity.kt @@ -1,4 +1,4 @@ -package ml.dandevelop.info_tren +package xyz.dcdevelop.info_tren import io.flutter.embedding.android.FlutterActivity diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 00fa441..d74aa35 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ - + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index ad28c11..e798ce8 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="xyz.dcdevelop.info_tren"> diff --git a/android/build.gradle b/android/build.gradle index b7faad8..ed45c65 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -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') } diff --git a/android/gradle.properties b/android/gradle.properties index 2bd6f4f..94adc3a 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-Xmx1536M - +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2819f02..bc6a58a 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/android/settings.gradle b/android/settings.gradle index 5a2f14f..44e62bc 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -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" diff --git a/fonts/ah/ah-Bold.ttf b/fonts/ah/ah-Bold.ttf new file mode 100644 index 0000000..14b7196 Binary files /dev/null and b/fonts/ah/ah-Bold.ttf differ diff --git a/fonts/ah/ah-BoldItalic.ttf b/fonts/ah/ah-BoldItalic.ttf new file mode 100644 index 0000000..4532705 Binary files /dev/null and b/fonts/ah/ah-BoldItalic.ttf differ diff --git a/fonts/ah/ah-Italic.ttf b/fonts/ah/ah-Italic.ttf new file mode 100644 index 0000000..89e5ce4 Binary files /dev/null and b/fonts/ah/ah-Italic.ttf differ diff --git a/fonts/ah/ah-Regular.ttf b/fonts/ah/ah-Regular.ttf new file mode 100644 index 0000000..c4fa6fb Binary files /dev/null and b/fonts/ah/ah-Regular.ttf differ diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..151026b --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,33 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f7..8d4492f 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index e8efba1..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -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" diff --git a/ios/Flutter/Flutter.podspec b/ios/Flutter/Flutter.podspec deleted file mode 100644 index 2c4421c..0000000 --- a/ios/Flutter/Flutter.podspec +++ /dev/null @@ -1,18 +0,0 @@ -# -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# This is a generated file; do not edit or check into version control. -# - -Pod::Spec.new do |s| - s.name = 'Flutter' - s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - # Framework linking is handled by Flutter tooling, not CocoaPods. - # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. - s.vendored_frameworks = 'path/to/nothing' -end diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 399e934..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -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" diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh index eafe31c..2d03b62 100755 --- a/ios/Flutter/flutter_export_environment.sh +++ b/ios/Flutter/flutter_export_environment.sh @@ -5,7 +5,6 @@ export "FLUTTER_APPLICATION_PATH=/Users/dan.cojocaru/info_tren" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_TARGET=/Users/dan.cojocaru/info_tren/lib/main.dart" export "FLUTTER_BUILD_DIR=build" -export "SYMROOT=${SOURCE_ROOT}/../build/ios" export "FLUTTER_BUILD_NAME=2.0.6" export "FLUTTER_BUILD_NUMBER=2.0.6" export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" diff --git a/ios/Podfile.lock b/ios/Podfile.lock deleted file mode 100644 index 5a029b1..0000000 --- a/ios/Podfile.lock +++ /dev/null @@ -1,22 +0,0 @@ -PODS: - - Flutter (1.0.0) - - webview_flutter (0.0.1): - - Flutter - -DEPENDENCIES: - - Flutter (from `Flutter`) - - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - webview_flutter: - :path: ".symlinks/plugins/webview_flutter/ios" - -SPEC CHECKSUMS: - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c - webview_flutter: 1aa7604e6cdb451a9b7ed2c37d5454c0b440246b - -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c - -COCOAPODS: 1.10.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7fbc945..4a5186f 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,15 +3,13 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 722F441253D3B79676E4DE80 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F72320B12B1F4015789BBC8E /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -33,12 +31,9 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 313F1E773DA06364A0C4F20A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 636963D381657D3BAEDC0A47 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 74CD890ACD2E394E606FCBEB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -47,7 +42,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F72320B12B1F4015789BBC8E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -55,21 +49,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 722F441253D3B79676E4DE80 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0B24EBF53F1DCC708FA961FD /* Frameworks */ = { - isa = PBXGroup; - children = ( - F72320B12B1F4015789BBC8E /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -87,8 +72,6 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - A2E7A2EB20EFBBAC4AB0299B /* Pods */, - 0B24EBF53F1DCC708FA961FD /* Frameworks */, ); sourceTree = ""; }; @@ -107,7 +90,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -116,23 +98,6 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; - A2E7A2EB20EFBBAC4AB0299B /* Pods */ = { - isa = PBXGroup; - children = ( - 313F1E773DA06364A0C4F20A /* Pods-Runner.debug.xcconfig */, - 74CD890ACD2E394E606FCBEB /* Pods-Runner.release.xcconfig */, - 636963D381657D3BAEDC0A47 /* Pods-Runner.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -140,14 +105,12 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 0D525F98970BF5A8EFFD825C /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 1B7EDCF8AB293318D8391906 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -165,18 +128,16 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; + ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = NF9A3KMT8Q; - LastSwiftMigration = 0910; - ProvisioningStyle = Automatic; + LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -200,7 +161,6 @@ files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -209,46 +169,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0D525F98970BF5A8EFFD825C /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 1B7EDCF8AB293318D8391906 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/webview_flutter/webview_flutter.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/webview_flutter.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -352,9 +272,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -365,27 +286,19 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = NF9A3KMT8Q; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 2.0.7; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren; + MARKETING_VERSION = 2.0.7; + PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -437,7 +350,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -486,10 +399,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -501,29 +416,19 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = NF9A3KMT8Q; + CURRENT_PROJECT_VERSION = 2.0.7; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren; + MARKETING_VERSION = 2.0.7; + PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -534,28 +439,18 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = NF9A3KMT8Q; + CURRENT_PROJECT_VERSION = 2.0.7; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren; + MARKETING_VERSION = 2.0.7; + PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 21a3cc1..1d526a1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,7 +4,4 @@ - - diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 71cc41e..70693e4 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Flutter @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 3d43d11..dc9ada4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 0218884..4a95797 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -17,24 +17,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion - $(FLUTTER_BUILD_NUMBER) + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS - NSAppTransportSecurity - - NSExceptionDomains - - cfr-scrapper.herokuapp.com - - NSExceptionAllowsInsecureHTTPLoads - - - - UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -42,6 +31,8 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad @@ -52,7 +43,5 @@ UIViewControllerBasedStatusBarAppearance - io.flutter.embedded_views_preview - diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h index 7335fdf..308a2a5 100644 --- a/ios/Runner/Runner-Bridging-Header.h +++ b/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/lib/api/train_data.dart b/lib/api/train_data.dart new file mode 100644 index 0000000..1ac8ece --- /dev/null +++ b/lib/api/train_data.dart @@ -0,0 +1,9 @@ +import 'package:http/http.dart' as http; +import 'package:info_tren/models/train_data.dart'; + +const AUTHORITY = 'scraper.infotren.dcdevelop.xyz'; + +Future getTrain(int trainNumber) async { + final response = await http.get(Uri.https(AUTHORITY, 'train/$trainNumber')); + return trainDataFromJson(response.body); +} \ No newline at end of file diff --git a/lib/components/cupertino_divider.dart b/lib/components/cupertino_divider.dart new file mode 100644 index 0000000..2012a69 --- /dev/null +++ b/lib/components/cupertino_divider.dart @@ -0,0 +1,60 @@ +import 'package:flutter/cupertino.dart'; +import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; + +class CupertinoDivider extends StatelessWidget { + final Color color; + + CupertinoDivider({Key? key, Color? color}): + color = color ?? FOREGROUND_DARK_GREY, + super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 1, + ), + Container( + height: 1, + decoration: BoxDecoration( + color: color, + ), + ), + Container( + height: 1, + ), + ], + ); + } +} + +class CupertinoVerticalDivider extends StatelessWidget { + final Color color; + + CupertinoVerticalDivider({Key? key, Color? color}): + color = color ?? FOREGROUND_DARK_GREY, + super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 1, + ), + Container( + width: 1, + decoration: BoxDecoration( + color: color, + ), + ), + Container( + width: 1, + ), + ], + ); + } +} diff --git a/lib/components/future_display.dart b/lib/components/future_display.dart new file mode 100644 index 0000000..075c117 --- /dev/null +++ b/lib/components/future_display.dart @@ -0,0 +1,42 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/utils/default_ui_design.dart'; + +class FutureDisplay extends StatelessWidget { + final UiDesign? uiDesign; + final Future future; + final Widget Function(BuildContext context, T data) builder; + final Widget Function(BuildContext context, Object error, StackTrace? st)? errorBuilder; + + FutureDisplay({Key? key, required this.future, required this.builder, this.errorBuilder, this.uiDesign}): super(key: key); + + @override + Widget build(BuildContext context) { + final uiDesign = this.uiDesign ?? defaultUiDesign; + return FutureBuilder( + future: future, + builder: (context, snapshot) { + if (snapshot.hasData) return builder(context, snapshot.data); + if (snapshot.hasError) return (errorBuilder != null ? errorBuilder!(context, snapshot.error!, snapshot.stackTrace) : throw snapshot.error!); + if (snapshot.connectionState == ConnectionState.done) return Container(); + + Widget loadingWidget; + switch (uiDesign) { + case UiDesign.MATERIAL: + loadingWidget = CircularProgressIndicator(); + break; + case UiDesign.CUPERTINO: + loadingWidget = CupertinoActivityIndicator(); + break; + default: + throw UnmatchedUiDesignException(uiDesign); + } + + return Center( + child: loadingWidget, + ); + }, + ); + } +} diff --git a/lib/components/loading/loading.dart b/lib/components/loading/loading.dart new file mode 100644 index 0000000..65c8670 --- /dev/null +++ b/lib/components/loading/loading.dart @@ -0,0 +1,31 @@ +import 'package:flutter/widgets.dart'; +import 'package:info_tren/components/loading/loading_cupertino.dart'; +import 'package:info_tren/components/loading/loading_material.dart'; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/utils/default_ui_design.dart'; + +class Loading extends StatelessWidget { + static const DEFAULT_TEXT = 'Loading...'; + + final UiDesign? uiDesign; + final String? text; + const Loading({ Key? key, this.text, this.uiDesign }) : super(key: key); + + @override + Widget build(BuildContext context) { + final uiDesign = this.uiDesign ?? defaultUiDesign; + switch (uiDesign) { + case UiDesign.MATERIAL: + return LoadingMaterial(text: text ?? DEFAULT_TEXT,); + case UiDesign.CUPERTINO: + return LoadingCupertino(text: text ?? DEFAULT_TEXT,); + default: + throw UnmatchedUiDesignException(uiDesign); + } + } +} + +abstract class LoadingCommon extends StatelessWidget { + final String text; + LoadingCommon({required this.text}); +} \ No newline at end of file diff --git a/lib/components/loading/loading_cupertino.dart b/lib/components/loading/loading_cupertino.dart new file mode 100644 index 0000000..8faa1b0 --- /dev/null +++ b/lib/components/loading/loading_cupertino.dart @@ -0,0 +1,28 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:info_tren/components/loading/loading.dart'; + +class LoadingCupertino extends LoadingCommon { + LoadingCupertino({required String text}) : super(text: text,); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: CupertinoActivityIndicator(), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text(text), + ), + ], + ), + ); + } + +} \ No newline at end of file diff --git a/lib/components/loading/loading_material.dart b/lib/components/loading/loading_material.dart new file mode 100644 index 0000000..6fccd10 --- /dev/null +++ b/lib/components/loading/loading_material.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:info_tren/components/loading/loading.dart'; + +class LoadingMaterial extends LoadingCommon { + LoadingMaterial({required String text}) : super(text: text,); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text(text), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/components/refresh_future_builder.dart b/lib/components/refresh_future_builder.dart new file mode 100644 index 0000000..f107ea0 --- /dev/null +++ b/lib/components/refresh_future_builder.dart @@ -0,0 +1,117 @@ +import 'package:flutter/widgets.dart'; + +class RefreshFutureBuilder extends StatefulWidget { + final Future Function()? futureCreator; + final T? initialData; + final Widget Function(BuildContext context, Future Function() refresh, RefreshFutureBuilderSnapshot snapshot) builder; + + const RefreshFutureBuilder({ Key? key, this.futureCreator, this.initialData, required this.builder }) : super(key: key); + + @override + _RefreshFutureBuilderState createState() => _RefreshFutureBuilderState(); +} + +class _RefreshFutureBuilderState extends State> { + late RefreshFutureBuilderSnapshot snapshot; + Future Function()? futureCreator; + + @override + void initState() { + super.initState(); + snapshot = widget.initialData != null ? RefreshFutureBuilderSnapshot.initial(widget.initialData!) : RefreshFutureBuilderSnapshot.nothing(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (futureCreator != widget.futureCreator) { + futureCreator = widget.futureCreator; + runFuture(); + } + } + + Future runFuture() async { + if (futureCreator == null) { + return; + } + // Set state to signify loading + setState(() { + switch (snapshot.state) { + case RefreshFutureBuilderState.none: + snapshot = RefreshFutureBuilderSnapshot.waiting(); + break; + case RefreshFutureBuilderState.initial: + snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data); + break; + case RefreshFutureBuilderState.waiting: + return; + case RefreshFutureBuilderState.error: + snapshot = RefreshFutureBuilderSnapshot.refresh(null, snapshot.error, snapshot.stackTrace); + break; + case RefreshFutureBuilderState.done: + snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data); + break; + case RefreshFutureBuilderState.refreshing: + return; + case RefreshFutureBuilderState.refreshError: + snapshot = RefreshFutureBuilderSnapshot.refresh(null, snapshot.error, snapshot.stackTrace); + break; + default: + } + }); + try { + final data = await futureCreator!(); + setState(() { + snapshot = RefreshFutureBuilderSnapshot.withData(data); + }); + } + catch (e, st) { + setState(() { + if (snapshot.state == RefreshFutureBuilderState.waiting) { + snapshot = RefreshFutureBuilderSnapshot.withError(e, st); + } + else { + snapshot = RefreshFutureBuilderSnapshot.refreshError(snapshot.data, e, st); + } + }); + } + } + + @override + Widget build(BuildContext context) { + return widget.builder( + context, + runFuture, + snapshot, + ); + } +} + +class RefreshFutureBuilderSnapshot { + final RefreshFutureBuilderState state; + final T? data; + final Object? error; + final StackTrace? stackTrace; + + bool get hasData => data != null; + bool get hasError => error != null; + + const RefreshFutureBuilderSnapshot._(this.state, this.data, this.error, this.stackTrace); + const RefreshFutureBuilderSnapshot.nothing() : state = RefreshFutureBuilderState.none, data = null, error = null, stackTrace = null; + const RefreshFutureBuilderSnapshot.initial(this.data) : state = RefreshFutureBuilderState.initial, error = null, stackTrace = null; + const RefreshFutureBuilderSnapshot.waiting() : state = RefreshFutureBuilderState.waiting, data = null, error = null, stackTrace = null; + const RefreshFutureBuilderSnapshot.withError(this.error, [this.stackTrace]) : state = RefreshFutureBuilderState.error, data = null; + const RefreshFutureBuilderSnapshot.withData(this.data) : state = RefreshFutureBuilderState.done, error = null, stackTrace = null; + const RefreshFutureBuilderSnapshot.refresh(this.data, [this.error, this.stackTrace]) : state = RefreshFutureBuilderState.refreshing; + const RefreshFutureBuilderSnapshot.refreshError(this.data, this.error, this.stackTrace) : state = RefreshFutureBuilderState.refreshError; +} + +enum RefreshFutureBuilderState { + none, + initial, + waiting, + error, + done, + refreshing, + refreshError, +} diff --git a/lib/components/select_train_suggestions/select_train_suggestions.dart b/lib/components/select_train_suggestions/select_train_suggestions.dart new file mode 100644 index 0000000..a88ab0b --- /dev/null +++ b/lib/components/select_train_suggestions/select_train_suggestions.dart @@ -0,0 +1,209 @@ +import 'dart:convert'; + +import 'package:flutter/widgets.dart'; +import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_cupertino.dart'; +import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_material.dart'; +import 'package:info_tren/models/train_operator_lines.dart'; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/utils/default_ui_design.dart'; +import 'package:tuple/tuple.dart'; + +class SelectTrainSuggestions extends StatefulWidget { + final UiDesign? uiDesign; + final String userInput; + final void Function(int trainNumber) onTrainSelected; + + const SelectTrainSuggestions({ Key? key, required this.uiDesign, required this.userInput, required this.onTrainSelected }) : super(key: key); + + @override + SelectTrainSuggestionsState createState() { + final uiDesign = this.uiDesign ?? defaultUiDesign; + switch(uiDesign) { + case UiDesign.MATERIAL: + return SelectTrainSuggestionsStateMaterial(); + case UiDesign.CUPERTINO: + return SelectTrainSuggestionsStateCupertino(); + default: + throw UnmatchedUiDesignException(uiDesign); + } + } +} + +abstract class SelectTrainSuggestionsState extends State { + late String userInput; + + List operators = []; + + Future loadOperators(BuildContext context) async { + operators = []; + + final operatorsString = await DefaultAssetBundle.of(context).loadString("assets/lines/files.txt"); + final operatorsFilesList = operatorsString.split("\n"); + + final decoder = JsonDecoder(); + + for (final operatorFile in operatorsFilesList) { + final operatorString = await DefaultAssetBundle.of(context).loadString("assets/lines/$operatorFile"); + final operatorData = decoder.convert(operatorString); + final _operator = TrainOperatorLines.fromJson(operatorData); + operators.add(_operator); + } + } + + @override + void initState() { + super.initState(); + userInput = widget.userInput; + + loadOperators(context).then((_) { + setState(() {}); + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (userInput != widget.userInput) { + setState(() { + userInput = widget.userInput; + }); + } + } + + String getUseCurrentInputWidgetText(int currentInput) => 'Caută trenul cu numărul $currentInput'; + Widget getUseCurrentInputWidget(int currentInput, void Function(int) onTrainSelected); + + @override + Widget build(BuildContext context) { + var sliversTuple = operators.map( + (op) => Tuple2( + getFilteredLines(op, userInput), + op.operator, + ) + ).where((tuple) => tuple.item1.isNotEmpty).toList(); + if (userInput.isNotEmpty) sliversTuple.sort((a, b) { + final aTrain = a.item1.first; + final bTrain = b.item1.first; + + final inputAsRegExp = RegExp(userInput); + + final matchOnA = inputAsRegExp.firstMatch(aTrain.number)!; + final matchOnB = inputAsRegExp.firstMatch(bTrain.number)!; + + if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start; + + if (aTrain.number.length != bTrain.number.length) return aTrain.number.length - bTrain.number.length; + + return aTrain.number.compareTo(bTrain.number); + }); + var slivers = sliversTuple.map((tuple) => OperatorAutocompleteSliver( + uiDesign: widget.uiDesign, + operatorName: tuple.item2, + trains: tuple.item1, + onTrainSelected: widget.onTrainSelected, + )).toList(); + + return CustomScrollView( + slivers: [ + ...slivers, + SliverToBoxAdapter( + child: int.tryParse(userInput) != null ? getUseCurrentInputWidget(int.parse(userInput), widget.onTrainSelected) : Container(), + ), + SliverToBoxAdapter( + child: Container( + height: MediaQuery.of(context).viewPadding.bottom, + ), + ), + ], + ); + } + + List getFilteredLines(TrainOperatorLines _operator, String currentInput) { + if (currentInput.isNotEmpty) { + final filteredLines = _operator.trains + .where((elem) => elem.number.contains(currentInput)) + .toList(); + + filteredLines.sort((a, b) { + final inputAsRegExp = RegExp(currentInput); + + final matchOnA = inputAsRegExp.firstMatch(a.number)!; + final matchOnB = inputAsRegExp.firstMatch(b.number)!; + + if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start; + + if (a.number.length != b.number.length) return a.number.length - b.number.length; + + return a.number.compareTo(b.number); + }); + + return filteredLines; + } + else { + return _operator.trains; + } + } +} + +class OperatorAutocompleteSliver extends StatelessWidget { + final UiDesign? uiDesign; + final String operatorName; + final List trains; + final void Function(int) onTrainSelected; + + const OperatorAutocompleteSliver({ Key? key, required this.uiDesign, required this.operatorName, required this.trains, required this.onTrainSelected }) : super(key: key); + + Widget mapTrainToItem(TrainOperatorTrainDescription train) { + final uiDesign = this.uiDesign ?? defaultUiDesign; + switch (uiDesign) { + case UiDesign.MATERIAL: + return OperatorAutocompleteTileMaterial( + onTrainSelected: onTrainSelected, + operatorName: operatorName, + train: train, + ); + case UiDesign.CUPERTINO: + return OperatorAutocompleteTileCupertino( + onTrainSelected: onTrainSelected, + operatorName: operatorName, + train: train, + ); + default: + throw UnmatchedUiDesignException(uiDesign); + } + } + + @override + Widget build(BuildContext context) { + if (trains.isEmpty) { + return SliverToBoxAdapter(child: Container(),); + } + + return SliverPrototypeExtentList( + prototypeItem: Column( + children: [ + mapTrainToItem(TrainOperatorTrainDescription()), + ], + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + return Column( + children: [ + mapTrainToItem(trains[index]), + ], + ); + }, + childCount: trains.length, + addSemanticIndexes: true, + ), + ); + } +} + +abstract class OperatorAutocompleteTile extends StatelessWidget { + final String operatorName; + final TrainOperatorTrainDescription train; + final void Function(int) onTrainSelected; + + const OperatorAutocompleteTile({ Key? key, required this.onTrainSelected, required this.operatorName, required this.train }) : super(key: key); +} diff --git a/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart b/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart new file mode 100644 index 0000000..d35d8b4 --- /dev/null +++ b/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart @@ -0,0 +1,74 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; +import 'package:info_tren/models/train_operator_lines.dart'; + +class SelectTrainSuggestionsStateCupertino extends SelectTrainSuggestionsState { + @override + Widget getUseCurrentInputWidget(int currentInput, void Function(int p1) onTrainSelected) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () { + onTrainSelected(currentInput); + }, + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(getUseCurrentInputWidgetText(currentInput)), + ], + ) + ), + ), + Divider(), + ], + ); + } +} + +class OperatorAutocompleteTileCupertino extends OperatorAutocompleteTile { + OperatorAutocompleteTileCupertino({ + Key? key, + required String operatorName, + required void Function(int) onTrainSelected, + required TrainOperatorTrainDescription train + }): super( + onTrainSelected: onTrainSelected, + operatorName: operatorName, + train: train, + key: key, + ); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + onTrainSelected(train.internalNumber); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 2, 16, 2), + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + operatorName, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200), + textAlign: TextAlign.left, + ), + Text( + "${train.rang} ${train.number}", + textAlign: TextAlign.left, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/components/select_train_suggestions/select_train_suggestions_material.dart b/lib/components/select_train_suggestions/select_train_suggestions_material.dart new file mode 100644 index 0000000..b8526b4 --- /dev/null +++ b/lib/components/select_train_suggestions/select_train_suggestions_material.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; +import 'package:info_tren/models/train_operator_lines.dart'; + +class SelectTrainSuggestionsStateMaterial extends SelectTrainSuggestionsState { + @override + Widget getUseCurrentInputWidget(int currentInput, void Function(int) onTrainSelected) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text(getUseCurrentInputWidgetText(currentInput)), + onTap: () { + onTrainSelected(currentInput); + }, + ), + Divider(), + ], + ); + } +} + +class OperatorAutocompleteTileMaterial extends OperatorAutocompleteTile { + OperatorAutocompleteTileMaterial({ + Key? key, + required String operatorName, + required void Function(int) onTrainSelected, + required TrainOperatorTrainDescription train + }): super( + onTrainSelected: onTrainSelected, + operatorName: operatorName, + train: train, + key: key, + ); + + @override + Widget build(BuildContext context) { + return ListTile( + dense: true, + title: Text("${train.rang} ${train.number}"), + subtitle: Text(operatorName), + onTap: () { + onTrainSelected(train.internalNumber); + }, + ); + } +} diff --git a/lib/components/slim_app_bar.dart b/lib/components/slim_app_bar.dart new file mode 100644 index 0000000..7e74411 --- /dev/null +++ b/lib/components/slim_app_bar.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +class SlimAppBar extends StatelessWidget { + final String title; + final double size; + // final Function onBackTap; + + SlimAppBar({ + required this.title, + this.size = 24, + // this.onBackTap, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + height: size, + child: Container( + color: + Theme.of(context).appBarTheme.color ?? + Theme.of(context).primaryColor, + child: InkWell( + onTap: (ModalRoute.of(context)?.canPop ?? false) + ? () => Navigator.of(context).pop() + : null, + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + height: size, + width: size, + child: (ModalRoute.of(context)?.canPop ?? false) + ? BackButtonIcon() + : null, + ), + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Text( + title, + textAlign: TextAlign.center, + style: + Theme.of(context).appBarTheme.textTheme?.caption?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.bodyText2?.color) ?? + Theme.of(context).textTheme.caption?.copyWith(color: Theme.of(context).textTheme.bodyText2?.color), + ), + ), + ), + ), + Container( + height: size, + width: size, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/hidden_webview.dart b/lib/hidden_webview.dart deleted file mode 100644 index 236787d..0000000 --- a/lib/hidden_webview.dart +++ /dev/null @@ -1,23 +0,0 @@ - -import 'package:flutter/widgets.dart'; -import 'package:webview_flutter/webview_flutter.dart'; - -class HiddenWebView extends StatelessWidget { - final WebView webView; - final Widget child; - - HiddenWebView({@required this.child, this.webView}); - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Offstage( - offstage: true, - child: webView, - ), - Positioned.fill(child: child) - ], - ); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 9bfcabe..89e53dc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,195 +2,73 @@ import 'dart:io' show Platform; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:info_tren/models/train_data.dart'; -import 'package:info_tren/train_info_page/train_info.dart'; -import 'package:info_tren/train_info_page/train_info_cupertino.dart'; -import 'package:info_tren/train_info_page/train_info_material.dart'; -import 'package:info_tren/train_info_page/train_info_prompt.dart'; +// import 'package:flutter_redux/flutter_redux.dart'; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/pages/main/main_page.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; +import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; -void main() => runApp(StartPoint()); +void main() { + // final store = createStore(); + // runApp( + // StoreProvider( + // store: store, + // child: StartPoint(), + // ) + // ); + runApp( + StartPoint(), + ); +} + +Map routesByUiDesign(UiDesign uiDesign) => { + Navigator.defaultRouteName: (context) { + return MainPage(uiDesign: uiDesign,); + }, + SelectTrainPage.routeName: (context) { + return SelectTrainPage(uiDesign: uiDesign); + }, + TrainInfo.routeName: (context) { + return TrainInfo( + trainNumber: ModalRoute.of(context)!.settings.arguments as int, + ); + }, +}; class StartPoint extends StatelessWidget { + final String appTitle = 'Info Tren'; + @override Widget build(BuildContext context) { - if (Platform.isAndroid) { + if (Platform.isIOS) { + return CupertinoApp( + title: appTitle, + theme: CupertinoThemeData( + primaryColor: Colors.blue.shade600, + brightness: Brightness.dark, + // textTheme: CupertinoTextThemeData( + // textStyle: TextStyle( + // fontFamily: 'Atkinson Hyperlegible', + // ), + // ), + ), + routes: routesByUiDesign(UiDesign.CUPERTINO), + ); + } + else { return MaterialApp( - title: 'Info Tren', + title: appTitle, theme: ThemeData( primarySwatch: Colors.blue, brightness: Brightness.dark, primaryColor: Colors.blue.shade600, accentColor: Colors.blue.shade700, + // fontFamily: 'Atkinson Hyperlegible', ), -// home: MainPageMaterial(), - routes: { - Navigator.defaultRouteName: (context) { - return MainPageMaterial(); - }, - TrainInfoPromptCommon.routeName: (context) { - return TrainInfoPromptMaterial(); - }, - TrainInfo.routeName: (context) { - return TrainDataWebViewAdapter( - builder: (context) { - return TrainInfoMaterial( - trainNumber: ModalRoute.of(context).settings.arguments as int, - ); - }, - ); - }, - }, - ); - } - 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, - ); - }, - ); - }, - } + routes: routesByUiDesign(UiDesign.MATERIAL), ); } - return null; - } -} - -mixin MainPageAction { - onTrainInfoPageInvoke(BuildContext context) { - Navigator.of(context).pushNamed(TrainInfoPromptCommon.routeName); - } - - onStationBoardPageInvoke(BuildContext context) { - - } - - onRoutePlanPageInvoke(BuildContext context) { - - } -} - -class MainPageMaterial extends StatelessWidget with MainPageAction { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Info Tren"), - centerTitle: true, - ), - body: SafeArea( - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton( - child: Text( - "Informații despre tren", - style: Theme.of(context).textTheme.button.copyWith(fontSize: 18), - ), - onPressed: () { - onTrainInfoPageInvoke(context); - }, - ), - ElevatedButton( - child: Text( - "Tabelă plecari/sosiri", - style: Theme.of(context).textTheme.button.copyWith(fontSize: 18), - ), - // TODO: Implement departure/arrival - onPressed: null, - // onPressed: () { - // onStationBoardPageInvoke(context); - // }, - ), - ElevatedButton( - child: Text( - "Planificare rută", - style: Theme.of(context).textTheme.button.copyWith(fontSize: 18), - ), - // TODO: Implement route planning - onPressed: null, - // onPressed: () { - // onRoutePlanPageInvoke(context); - // }, - ) - ].map((w) => Padding( - padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), - child: SizedBox( - width: double.infinity, - child: w, - ), - )).toList(), - ), - ), - ), - ); } } - -class MainPageCupertino extends StatelessWidget with MainPageAction { - @override - Widget build(BuildContext context) { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text("Info Tren"), - ), - child: SafeArea( - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CupertinoButton.filled( - child: Text("Informații despre tren"), - onPressed: () { - onTrainInfoPageInvoke(context); - }, - ), - CupertinoButton.filled( - child: Text("Tabelă plecari/sosiri"), - // TODO: Implement departure/arrival - onPressed: null, - // onPressed: () { - // onStationBoardPageInvoke(context); - // }, - ), - CupertinoButton.filled( - child: Text("Planificare rută"), - // TODO: Implement route planning - onPressed: null, - // onPressed: () { - // onRoutePlanPageInvoke(context); - // }, - ), - ].map((w) => Padding( - padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), - child: SizedBox( - width: double.infinity, - child: w, - ), - )).toList(), - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/models/train_data.dart b/lib/models/train_data.dart index 8bc9ac9..7469334 100644 --- a/lib/models/train_data.dart +++ b/lib/models/train_data.dart @@ -1,1077 +1,229 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/widgets.dart'; -import 'package:info_tren/hidden_webview.dart'; -import 'package:info_tren/utils/webview_invoke.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:webview_flutter/webview_flutter.dart'; - -part 'train_data.g.dart'; - -enum TrainLookupResult { - FOUND, - NOT_FOUND, - OTHER -} - -class OnDemandInvalidatedException implements Exception { - final String propertyName; - final OnDemand onDemandClass; - - OnDemandInvalidatedException({this.propertyName, this.onDemandClass}); - - @override - String toString() { - return "OnDemandInvalidatedException: An attempt to get $propertyName from ${onDemandClass.runtimeType} failed because the source was invalidated."; - } -} - -class OnDemand { - bool valid; - - final Function onInvalidation; - - void invalidate() { - if (valid) { - valid = false; - if (onInvalidation != null) onInvalidation(); - } - } - - OnDemand(this.onInvalidation): valid = true; -} - -class OnDemandTrainData extends OnDemand { - final WebViewController _controller; - - OnDemandTrainData({ - WebViewController controller, - Function onInvalidation - }) - : _controller = controller, - _route = OnDemandTrainRoute(controller: controller), - _lastInfo = OnDemandLastInfo(controller: controller), - _destination = OnDemandDestination(controller: controller), - _nextStop = OnDemandNextStop(controller: controller), - _stations = OnDemandStations(controller: controller), - super(onInvalidation); - - @override - invalidate() { - super.invalidate(); - route.invalidate(); - lastInfo.invalidate(); - destination.invalidate(); - nextStop.invalidate(); - stations.invalidate(); - } - - Future get _originalDepartureDate async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDepartureDate"); - - final tempRes = await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let field = table.querySelector("caption"); - return field.textContent.trim(); - })() - """, - isFunctionAlready: true, - ); - - return tempRes.split(" ").last; - } - - Future get departureDate async { - final str = await _originalDepartureDate; - - final parts = str.split(".").map((str) => int.parse(str)).toList(); - - return DateTime(parts[2], parts[1], parts[0]); - } - - Future get rang async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "rang"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[0]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - ); - } - - Future get trainNumber async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "trainNumber"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[1]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - ); - } - - Future get operator async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "operator"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[2]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - ); - } - - final OnDemandTrainRoute _route; - OnDemandTrainRoute get route => _route; - - Future get state async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "state"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[4]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - ); - } - - final OnDemandLastInfo _lastInfo; - OnDemandLastInfo get lastInfo => _lastInfo; - - final OnDemandDestination _destination; - OnDemandDestination get destination => _destination; - - final OnDemandNextStop _nextStop; - OnDemandNextStop get nextStop => _nextStop; - - Future get routeDistance async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "routeDistance"); - - final result = (await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[12]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - )).trim(); - - return takeWhile(result, (char) => char != ' '.codeUnitAt(0)); - } - - Future get _routeDuration async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_routeDuration"); - - var result = (await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[13]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - )).trim(); - - if (result[result.length - 1] == '.') result = result.substring(0, result.length - 1); - - return result; - } - - Future get routeDuration async { - final input = await _routeDuration; - - var result = Duration(); - - StringBuffer buffer = StringBuffer(); - - for (var i = 0; i < input.length; i++) { - if ('0'.codeUnitAt(0) <= input.codeUnitAt(i) && input.codeUnitAt(i) <= '9'.codeUnitAt(0)) { - buffer.writeCharCode(input.codeUnitAt(i)); - } - else if (input.startsWith("min", i)) { - result += Duration(minutes: int.parse(buffer.toString())); - buffer = StringBuffer(); - i += 2; - } - else if (input.startsWith("h", i)) { - result += Duration(hours: int.parse(buffer.toString())); - buffer = StringBuffer(); - } - else throw FormatException("Unrecognised!"); - } - - return result; - } - - final OnDemandStations _stations; - OnDemandStations get stations => _stations; -} - -class OnDemandTrainRoute extends OnDemand { - final WebViewController _controller; - - OnDemandTrainRoute({ - WebViewController controller, - Function onInvalidation - }) : _controller = controller, super(onInvalidation); - - Future get original async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "original"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[3]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - ); - } - - Future get from async { - final original = await this.original; - return original.split("-")[0]; - } - - Future get to async { - final original = await this.original; - return original.split("-")[1]; - } -} - -class OnDemandLastInfo extends OnDemand { - final WebViewController _controller; - - OnDemandLastInfo({ - WebViewController controller, - Function onInvalidation - }) : _controller = controller, super(onInvalidation); - - Future get _lastInfoOriginal async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_lastInfoOriginal"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[5]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - ); - } - - Future get station async { - final original = await _lastInfoOriginal; - - return original - .split("[")[0] - .trim(); - } - - Future get event async { - final original = await _lastInfoOriginal; - - return original - .split("[")[1] - .split("]")[0] - .trim(); - } - - Future get originalDateAndTime async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "originalDateAndTime"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[6]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - ); - } - - Future get dateAndTime async => parseCFRDateTime(await originalDateAndTime); - - Future get delay async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "delay"); - - return int.parse( - await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[7]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - ) - ); - } -} +// To parse this JSON data, do +// +// final trainData = trainDataFromJson(jsonString); -class OnDemandDestination extends OnDemand { - final WebViewController _controller; - - OnDemandDestination({ - WebViewController controller, - Function onInvalidation - }) : _controller = controller, super(onInvalidation); - - Future get stationName async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "destinationStation"); - - final result = (await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[8]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - )).trim(); - - if (result.isEmpty) return null; - else return result; - } - - Future get _originalDestinationArrival async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDestinationArrival"); - - final result = (await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[9]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - )).trim(); - - if (result.isEmpty) return null; - else return result; - } - - Future get arrival => _originalDestinationArrival.then((value) => parseCFRDateTime(value)); -} - -class OnDemandNextStop extends OnDemand { - final WebViewController _controller; - - OnDemandNextStop({ - WebViewController controller, - Function onInvalidation - }) : _controller = controller, super(onInvalidation); - - Future get stationName async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "destinationStation"); - - final result = (await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[10]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - )).trim(); - - if (result.isEmpty) return null; - else return result; - } - - Future get _originalNextStopArrival async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDestinationArrival"); - - final result = (await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - let rows = table.querySelectorAll("tr"); - let currentRow = rows[11]; - let currentDataCell = currentRow.querySelectorAll("td")[1]; - return currentDataCell.textContent; - })() - """, - isFunctionAlready: true, - )).trim(); - - if (result.isEmpty) return null; - else return result; - } - - Future get arrival => _originalNextStopArrival.then((value) => parseCFRDateTime(value)); -} - -class OnDemandStations extends OnDemand { - final WebViewController _controller; - List issuedOnDemands = []; - - @override - void invalidate() { - issuedOnDemands.map((od) => od.invalidate()); - super.invalidate(); - } - - OnDemandStations({ - @required WebViewController controller, - Function onInvalidation - }) : _controller = controller, super(onInvalidation); - - Future get _stationsLoaded async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_stationsLoaded"); - - final result = await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => JSON.stringify(document.querySelector("#GridView1") == null))() - """, - isFunctionAlready: true, - ); +import 'dart:convert'; - final decoder = JsonDecoder(); - return !(decoder.convert(result) as bool); - } +TrainData trainDataFromJson(String str) => TrainData.fromJson(json.decode(str)); - Stream call({@required Future pageLoadFuture}) async* { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "call"); +String trainDataToJson(TrainData data) => json.encode(data.toJson()); - if (!await _stationsLoaded) { - await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const button = document.querySelector("#Button2"); - button.click(); - })() - """, - isFunctionAlready: true, +/// Results of scrapping InfoFer website for train info +class TrainData { + TrainData({ + required this.date, + required this.number, + required this.operator, + required this.rank, + required this.route, + required this.stations, + this.status, + }); + + final String date; + final String number; + final String operator; + final String rank; + final Route route; + final List stations; + final TrainDataStatus? status; + + factory TrainData.fromJson(Map json) => TrainData( + date: json["date"], + number: json["number"], + operator: json["operator"], + rank: json["rank"], + route: Route.fromJson(json["route"]), + stations: List.from( + json["stations"].map((x) => Station.fromJson(x))), + status: json["status"] == null + ? null + : TrainDataStatus.fromJson(json["status"]), ); - await pageLoadFuture; - } - - final count = int.parse(await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const count = rowsArray.length - 1; - return String(count); - })() - """, - isFunctionAlready: true, - )); - for (int i = 1; i <= count; i++) { - final ods = OnDemandStation( - controller: _controller, - index: i, + Map toJson() => { + "date": date, + "number": number, + "operator": operator, + "rank": rank, + "route": route.toJson(), + "stations": List.from(stations.map((x) => x.toJson())), + "status": status?.toJson(), + }; +} + +/// Route of the train +class Route { + Route({ + required this.from, + required this.to, + }); + + final String from; + final String to; + + factory Route.fromJson(Map json) => Route( + from: json["from"], + to: json["to"], ); - issuedOnDemands.add(ods); - yield ods; - } - } -} - -class OnDemandStation extends OnDemand { - final WebViewController _controller; - final int index; - - OnDemandStation({ - @required WebViewController controller, - @required this.index, - Function onInvalidation - }) : _controller = controller, super(onInvalidation); - - Future get km async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "km"); - - return int.parse(await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const row = rowsArray[$index]; - const columns = row.querySelectorAll("td"); - const kmCell = columns[0]; - return kmCell.textContent; - })() - """, - isFunctionAlready: true, - )); - } - - Future get stationName async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "stationName"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const row = rowsArray[$index]; - const columns = row.querySelectorAll("td"); - const kmCell = columns[1]; - return kmCell.textContent; - })() - """, - isFunctionAlready: true, - ); - } - - Future get arrivalTime async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "arrivalTime"); - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const row = rowsArray[$index]; - const columns = row.querySelectorAll("td"); - const kmCell = columns[2]; - return kmCell.textContent.trim(); - })() - """, - isFunctionAlready: true, - ); - } - - Future get stopsFor async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "stopsFor"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const row = rowsArray[$index]; - const columns = row.querySelectorAll("td"); - const kmCell = columns[3]; - return kmCell.textContent.trim(); - })() - """, - isFunctionAlready: true, - ); - } - - Future get departureTime async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "departureTime"); - - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const row = rowsArray[$index]; - const columns = row.querySelectorAll("td"); - const kmCell = columns[4]; - return kmCell.textContent.trim(); - })() - """, - isFunctionAlready: true, - ); - } - - Future get realOrEstimate async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "realOrEstimate"); - - final value = await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const row = rowsArray[$index]; - const columns = row.querySelectorAll("td"); - const kmCell = columns[5]; - return kmCell.textContent.trim(); - })() - """, - isFunctionAlready: true, - ); - - if (value == "Real") return RealOrEstimate.real; - else if (value == "Estimat") return RealOrEstimate.estimate; - else return RealOrEstimate.UNKNOWN; - } + Map toJson() => { + "from": from, + "to": to, + }; +} + +class Station { + Station({ + this.arrival, + this.departure, + required this.km, + required this.name, + this.platform, + this.stoppingTime, + }); + + final StationArrDepTime? arrival; + final StationArrDepTime? departure; + final int km; + final String name; + final String? platform; + final int? stoppingTime; + + factory Station.fromJson(Map json) => Station( + arrival: json["arrival"] == null + ? null + : StationArrDepTime.fromJson(json["arrival"]), + departure: json["departure"] == null + ? null + : StationArrDepTime.fromJson(json["departure"]), + km: json["km"], + name: json["name"], + platform: json["platform"] == null ? null : json["platform"], + stoppingTime: + json["stoppingTime"] == null ? null : json["stoppingTime"], + ); - Future get delay async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "delay"); + Map toJson() => { + "arrival": arrival?.toJson(), + "departure": departure?.toJson(), + "km": km, + "name": name, + "platform": platform == null ? null : platform, + "stoppingTime": stoppingTime == null ? null : stoppingTime, + }; +} + +class StationArrDepTime { + StationArrDepTime({ + required this.scheduleTime, + this.status, + }); + + final String scheduleTime; + final StationArrDepTimeStatus? status; + + factory StationArrDepTime.fromJson(Map json) => + StationArrDepTime( + scheduleTime: json["scheduleTime"], + status: json["status"] == null ? null : StationArrDepTimeStatus.fromJson(json["status"]), + ); - final value = await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const row = rowsArray[$index]; - const columns = row.querySelectorAll("td"); - const kmCell = columns[6]; - return kmCell.textContent.trim(); - })() - """, - isFunctionAlready: true, - ); + Map toJson() => { + "scheduleTime": scheduleTime, + "status": status?.toJson(), + }; +} - if (value.isEmpty) return 0; - else return int.parse(value); - } +class StationArrDepTimeStatus { + StationArrDepTimeStatus({ + required this.delay, + required this.real, + }); - Future get observations async { - if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "observations"); + final int delay; + final bool real; - return await wInvoke( - webViewController: _controller, - jsFunctionContent: """ - (() => { - const table = document.querySelector("#GridView1"); - const rows = table.querySelectorAll("tr"); - const rowsArray = Array.from(rows); - const row = rowsArray[$index]; - const columns = row.querySelectorAll("td"); - const kmCell = columns[7]; - return kmCell.textContent.trim(); - })() - """, - isFunctionAlready: true, - ); - } -} + factory StationArrDepTimeStatus.fromJson(Map json) => + StationArrDepTimeStatus( + delay: json["delay"], + real: json["real"], + ); -enum RealOrEstimate { - real, - estimate, - UNKNOWN + Map toJson() => { + "delay": delay, + "real": real, + }; } -class TrainDataWebViewAdapter extends StatefulWidget { - final WidgetBuilder builder; - - TrainDataWebViewAdapter({@required this.builder}); - - @override - State createState() { - return _TrainDataWebViewAdapterState(); - } +class TrainDataStatus { + TrainDataStatus({ + required this.delay, + required this.state, + required this.station, + }); - static _TrainDataWebViewAdapterState of(BuildContext context) => - (context.findAncestorWidgetOfExactType<_TrainDataWebViewAdapterInheritedWidget>()) - .state; -} + final int delay; + final State state; + final String station; -class ProgressReport { - final int current; - final int total; - final String description; + factory TrainDataStatus.fromJson(Map json) => + TrainDataStatus( + delay: json["delay"], + state: stateValues.map[json["state"]]!, + station: json["station"], + ); - ProgressReport({@required this.current, @required this.total, this.description}); + Map toJson() => { + "delay": delay, + "state": stateValues.reverse[state], + "station": station, + }; @override String toString() { - return description == null ? "ProgressReport($current/$total)" : "ProgressReport($current/$total: $description)"; - } -} - -class _TrainDataWebViewAdapterState extends State { - Completer _webViewControllerCompleter = Completer(); - Future get webViewController => _webViewControllerCompleter.future; - - StreamController _pageLoadController; - Stream pageLoadStream; - Future get nextLoadFuture => pageLoadStream.take(1).first; - - StreamController _progressController; - Stream progressStream; - - Future loadTrain(int trainNo) async { - currentDatas.removeWhere((ondemand) { - ondemand.invalidate(); - return true; - }); - - final controller = await webViewController; - var nlf; - - nlf = nextLoadFuture; - await controller.loadUrl("https://appiris.infofer.ro/MytrainRO.aspx"); - await nlf; - _reportStatus( - current: 2, - description: "Loaded Informatica Feroviară webpage" - ); - - nlf = nextLoadFuture; - await controller.evaluateJavascript(""" - ( () => { - let inputField = document.querySelector("#TextTrnNo"); - inputField.value = $trainNo; - let submitButton = document.querySelector("#Button1"); - submitButton.click(); - } ) () - """); - await nlf; - - _reportStatus( - current: 3, - description: "Loaded train information" - ); - - var result = await wInvoke( - webViewController: controller, - jsFunctionContent: """ - (() => { - let errorMessage = document.querySelector("#Lblx"); - return errorMessage.textContent; - })() - """, - isFunctionAlready: true, - ); - - if (result.isNotEmpty) { - return TrainLookupResult.NOT_FOUND; + String result = ''; + if (delay == 0) { + result += 'La timp'; } - - final jsonDecoder = JsonDecoder(); - - final foundTable = jsonDecoder.convert(await wInvoke( - webViewController: controller, - jsFunctionContent: """ - (() => { - let table = document.querySelector("#DetailsView1"); - return JSON.stringify(table !== null); - })() - """, - isFunctionAlready: true, - )) as bool; - - if (foundTable) { - return TrainLookupResult.FOUND; + else { + result += '${delay.abs()} min'; } - - /// Should not happen, report error in this case - return TrainLookupResult.OTHER; - } - - List currentDatas = []; - - Future trainData({Function onInvalidation}) async { - final controller = await webViewController; - - final result = OnDemandTrainData( - controller: controller, - onInvalidation: onInvalidation - ); - - currentDatas.add(result); - - return result; - } - - int lastStatusReported; - - _reportStatus({@required int current, String description}) { - lastStatusReported = current; - _progressController.add(ProgressReport( - current: current, - total: TOTAL_PROGRESS, - description: description - )); - } - - recallLastReport() { - _progressController.add(ProgressReport(current: lastStatusReported, total: TOTAL_PROGRESS)); - } - - restartProgressReport() { - lastStatusReported = 0; - webViewController.then((_) { - _reportStatus(current: 1, description: "WebView created"); - }); - } - - @override - void initState() { - _pageLoadController = StreamController(); - pageLoadStream = _pageLoadController.stream.asBroadcastStream(); - - _progressController = StreamController(); - progressStream = _progressController.stream.asBroadcastStream(); - - lastStatusReported = 0; - - super.initState(); - } - - @override - void dispose() { - _pageLoadController.close(); - _progressController.close(); - - super.dispose(); - } - - static const int TOTAL_PROGRESS = 3; - - @override - Widget build(BuildContext context) { - return HiddenWebView( - webView: WebView( - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (controller) { - _webViewControllerCompleter.complete(controller); - _reportStatus( - current: 1, - description: "WebView created" - ); - }, - onPageFinished: (url) { - _pageLoadController.add(url); - }, - ), - child: _TrainDataWebViewAdapterInheritedWidget( - child: Builder( - builder: widget.builder, - ), - state: this, - ), - ); - } -} - -class _TrainDataWebViewAdapterInheritedWidget extends InheritedWidget { - final _TrainDataWebViewAdapterState state; - - _TrainDataWebViewAdapterInheritedWidget({this.state, Widget child, Key key}) - :super(key: key, child: child); - - @override - bool updateShouldNotify(InheritedWidget oldWidget) { - return true; - } -} - -@JsonSerializable() -class TrainData { - final String rang; - @JsonKey(name: "tren") - final String trainNumber; - final String operator; - @JsonKey(name: "relatia") - final String route; - @JsonKey(name: "stare") - final String state; - @JsonKey(name: "ultima_informatie") - final LastInfo lastInfo; - @JsonKey(name: "destinatie") - final StopInfo destination; - @JsonKey(name: "urmatoarea_oprire") - final StopInfo nextStop; - @JsonKey(name: "durata_calatoriei") - final String tripLength; - @JsonKey(name: "distanta") - final String distance; - - @JsonKey(name: "stations") - List stations; - - TrainData({this.rang, this.trainNumber, this.operator, this.lastInfo, - this.state, this.route, this.tripLength, this.stations, this.nextStop, - this.distance, this.destination}); - factory TrainData.fromJson(Map json) { - var result = _$TrainDataFromJson(json); - var foundEstimat = false; - result.stations = result.stations.map((station) { - if (station.realOrEstimate == "Estimat") { - foundEstimat = true; - } - - station.realOrEstimate = foundEstimat ? "Estimat" : "Real"; - - return station; - }).toList(); + result += ' la '; + switch (state) { + case State.PASSING: + result += 'trecerea fără oprire prin'; + break; + case State.ARRIVAL: + result += 'sosirea în'; + break; + case State.DEPARTURE: + result += 'plecarea din'; + break; + } + result += station; return result; } - Map toJson() => _$TrainDataToJson(this); } -@JsonSerializable() -class LastInfo { - @JsonKey(name: "statia") - final String station; - @JsonKey(name: "eveniment") - final String event; - @JsonKey(name: "data_si_ora") - final String dateAndTime; - DateTime get formattedDateAndTime { - return parseCFRDateTime(dateAndTime); - } - @JsonKey(name: "intarziere") - final int delay; - - - LastInfo({this.dateAndTime, this.delay, this.event, this.station}); +enum State { PASSING, ARRIVAL, DEPARTURE } - factory LastInfo.fromJson(Map json) => _$LastInfoFromJson(json); - Map toJson() => _$LastInfoToJson(this); -} - -@JsonSerializable() -class StopInfo { - @JsonKey(name: "statia") - final String station; - @JsonKey(name: "data_si_ora") - final String dateAndTime; - DateTime get formattedDateAndTime { - return parseCFRDateTime(dateAndTime); - } +final stateValues = EnumValues({ + "arrival": State.ARRIVAL, + "departure": State.DEPARTURE, + "passing": State.PASSING +}); - StopInfo({this.station, this.dateAndTime}); +class EnumValues { + Map map; + Map? reverseMap; - factory StopInfo.fromJson(Map json) => _$StopInfoFromJson(json); - Map toJson() => _$StopInfoToJson(this); -} + EnumValues(this.map); -@JsonSerializable() -class StationEntry { - final String km; - @JsonKey(name: "statia") - final String name; - @JsonKey(name: "sosire") - final String arrivalTime; - @JsonKey(name: "stationeaza_pentru") - final String waitTime; - @JsonKey(name: "plecare") - final String departureTime; - @JsonKey(name: "real/estimat") - String realOrEstimate; - bool get real { - return realOrEstimate == "Real"; + Map get reverse { + if (reverseMap == null) { + reverseMap = map.map((k, v) => new MapEntry(v, k)); + } + return reverseMap!; } - @JsonKey(name: "intarziere") - final int delay; - @JsonKey(name: "observatii") - final String observations; - - StationEntry({this.name, this.delay, this.realOrEstimate, - this.arrivalTime, this.departureTime, this.km, this.observations, - this.waitTime}); - - factory StationEntry.fromJson(Map json) => _$StationEntryFromJson(json); - Map toJson() => _$StationEntryToJson(this); -} - -DateTime parseCFRDateTime(String dateAndTime) { - if (dateAndTime == null || dateAndTime.isEmpty) return null; - - final parts = dateAndTime.split(" "); - - final dateParts = parts[0].split("."); - final day = int.parse(dateParts[0]); - final month = int.parse(dateParts[1]); - final year = int.parse(dateParts[2]); - - final timeParts = parts[1].split(":"); - final hour = int.parse(timeParts[0]); - final minute = int.parse(timeParts[1]); - - return DateTime(year, month, day, hour, minute); } - -String takeWhile(String input, Function charValidator) { - StringBuffer output = StringBuffer(); - - for (final char in input.codeUnits) { - if (charValidator(char)) output.writeCharCode(char); - else break; - } - - return output.toString(); -} \ No newline at end of file diff --git a/lib/models/train_data.g.dart b/lib/models/train_data.g.dart deleted file mode 100644 index d90192d..0000000 --- a/lib/models/train_data.g.dart +++ /dev/null @@ -1,98 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'train_data.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -TrainData _$TrainDataFromJson(Map json) { - return TrainData( - rang: json['rang'] as String, - trainNumber: json['tren'] as String, - operator: json['operator'] as String, - lastInfo: json['ultima_informatie'] == null - ? null - : LastInfo.fromJson( - json['ultima_informatie'] as Map), - state: json['stare'] as String, - route: json['relatia'] as String, - tripLength: json['durata_calatoriei'] as String, - stations: (json['stations'] as List) - ?.map((e) => e == null - ? null - : StationEntry.fromJson(e as Map)) - ?.toList(), - nextStop: json['urmatoarea_oprire'] == null - ? null - : StopInfo.fromJson( - json['urmatoarea_oprire'] as Map), - distance: json['distanta'] as String, - destination: json['destinatie'] == null - ? null - : StopInfo.fromJson(json['destinatie'] as Map)); -} - -Map _$TrainDataToJson(TrainData instance) => { - 'rang': instance.rang, - 'tren': instance.trainNumber, - 'operator': instance.operator, - 'relatia': instance.route, - 'stare': instance.state, - 'ultima_informatie': instance.lastInfo, - 'destinatie': instance.destination, - 'urmatoarea_oprire': instance.nextStop, - 'durata_calatoriei': instance.tripLength, - 'distanta': instance.distance, - 'stations': instance.stations - }; - -LastInfo _$LastInfoFromJson(Map json) { - return LastInfo( - dateAndTime: json['data_si_ora'] as String, - delay: json['intarziere'] as int, - event: json['eveniment'] as String, - station: json['statia'] as String); -} - -Map _$LastInfoToJson(LastInfo instance) => { - 'statia': instance.station, - 'eveniment': instance.event, - 'data_si_ora': instance.dateAndTime, - 'intarziere': instance.delay - }; - -StopInfo _$StopInfoFromJson(Map json) { - return StopInfo( - station: json['statia'] as String, - dateAndTime: json['data_si_ora'] as String); -} - -Map _$StopInfoToJson(StopInfo instance) => { - 'statia': instance.station, - 'data_si_ora': instance.dateAndTime - }; - -StationEntry _$StationEntryFromJson(Map json) { - return StationEntry( - name: json['statia'] as String, - delay: json['intarziere'] as int, - realOrEstimate: json['real/estimat'] as String, - arrivalTime: json['sosire'] as String, - departureTime: json['plecare'] as String, - km: json['km'] as String, - observations: json['observatii'] as String, - waitTime: json['stationeaza_pentru'] as String); -} - -Map _$StationEntryToJson(StationEntry instance) => - { - 'km': instance.km, - 'statia': instance.name, - 'sosire': instance.arrivalTime, - 'stationeaza_pentru': instance.waitTime, - 'plecare': instance.departureTime, - 'real/estimat': instance.realOrEstimate, - 'intarziere': instance.delay, - 'observatii': instance.observations - }; diff --git a/lib/models/train_operator_lines.dart b/lib/models/train_operator_lines.dart new file mode 100644 index 0000000..ed408af --- /dev/null +++ b/lib/models/train_operator_lines.dart @@ -0,0 +1,42 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'train_operator_lines.g.dart'; + +@JsonSerializable() +class TrainOperatorLines { + @JsonKey(name: "short_name") + final String shortName; + final String operator; + @JsonKey(name: "versiune") + final String version; + @JsonKey(name: "trenuri") + final List trains; + + TrainOperatorLines({ + required this.operator, + this.shortName = "", + required this.version, + required this.trains, + }); + + factory TrainOperatorLines.fromJson(Map json) => _$TrainOperatorLinesFromJson(json); + Map toJson() => _$TrainOperatorLinesToJson(this); +} + +@JsonSerializable() +class TrainOperatorTrainDescription { + final String rang; + @JsonKey(name: "numar") + final String number; + @JsonKey(name: "numar_intern") + final int internalNumber; + + TrainOperatorTrainDescription({ + this.number = '', + this.rang = '', + this.internalNumber = 0, + }); + + factory TrainOperatorTrainDescription.fromJson(Map json) => _$TrainOperatorTrainDescriptionFromJson(json); + Map toJson() => _$TrainOperatorTrainDescriptionToJson(this); +} diff --git a/lib/models/train_operator_lines.g.dart b/lib/models/train_operator_lines.g.dart new file mode 100644 index 0000000..c1ab5ec --- /dev/null +++ b/lib/models/train_operator_lines.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'train_operator_lines.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TrainOperatorLines _$TrainOperatorLinesFromJson(Map json) => + TrainOperatorLines( + operator: json['operator'] as String, + shortName: json['short_name'] as String? ?? "", + version: json['versiune'] as String, + trains: (json['trenuri'] as List) + .map((e) => + TrainOperatorTrainDescription.fromJson(e as Map)) + .toList(), + ); + +Map _$TrainOperatorLinesToJson(TrainOperatorLines instance) => + { + 'short_name': instance.shortName, + 'operator': instance.operator, + 'versiune': instance.version, + 'trenuri': instance.trains, + }; + +TrainOperatorTrainDescription _$TrainOperatorTrainDescriptionFromJson( + Map json) => + TrainOperatorTrainDescription( + number: json['numar'] as String? ?? '', + rang: json['rang'] as String? ?? '', + internalNumber: json['numar_intern'] as int? ?? 0, + ); + +Map _$TrainOperatorTrainDescriptionToJson( + TrainOperatorTrainDescription instance) => + { + 'rang': instance.rang, + 'numar': instance.number, + 'numar_intern': instance.internalNumber, + }; diff --git a/lib/models/ui_design.dart b/lib/models/ui_design.dart new file mode 100644 index 0000000..19ebc75 --- /dev/null +++ b/lib/models/ui_design.dart @@ -0,0 +1,15 @@ +enum UiDesign { + MATERIAL, + CUPERTINO +} + +class UnmatchedUiDesignException implements Exception { + final UiDesign uiDesign; + + UnmatchedUiDesignException(this.uiDesign); + + @override + String toString() { + return '$uiDesign was not matched'; + } +} diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart new file mode 100644 index 0000000..6734556 --- /dev/null +++ b/lib/pages/main/main_page.dart @@ -0,0 +1,68 @@ +import 'package:flutter/widgets.dart'; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/pages/main/main_page_cupertino.dart'; +import 'package:info_tren/pages/main/main_page_material.dart'; +import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; +import 'package:info_tren/utils/default_ui_design.dart'; + +class MainPage extends StatelessWidget { + final UiDesign? uiDesign; + + const MainPage({ Key? key, this.uiDesign }) : super(key: key); + + @override + Widget build(BuildContext context) { + final uiDesign = this.uiDesign ?? defaultUiDesign; + + switch (uiDesign) { + case UiDesign.MATERIAL: + return MainPageMaterial(); + case UiDesign.CUPERTINO: + return MainPageCupertino(); + default: + throw UnmatchedUiDesignException(uiDesign); + } + } +} + +abstract class MainPageShared extends StatelessWidget { + final String pageTitle = 'Info Tren'; + + List get options => [ + MainPageOption( + name: 'Informații despre tren', + action: (BuildContext context) { + onTrainInfoPageInvoke(context); + }, + ), + MainPageOption( + name: 'Tabelă plecari/sosiri', + // TODO: Implement departure/arrival + action: null, + ), + MainPageOption( + name: 'Planificare rută', + // TODO: Implement route planning + action: null, + ), + ]; + + onTrainInfoPageInvoke(BuildContext context) { + Navigator.of(context).pushNamed(SelectTrainPage.routeName); + } + + onStationBoardPageInvoke(BuildContext context) { + + } + + onRoutePlanPageInvoke(BuildContext context) { + + } +} + +class MainPageOption { + final String name; + final void Function(BuildContext context)? action; + + MainPageOption({required this.name, this.action}); +} diff --git a/lib/pages/main/main_page_cupertino.dart b/lib/pages/main/main_page_cupertino.dart new file mode 100644 index 0000000..4e0d2df --- /dev/null +++ b/lib/pages/main/main_page_cupertino.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:info_tren/pages/main/main_page.dart'; + +class MainPageCupertino extends MainPageShared { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(pageTitle), + ), + child: SafeArea( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: options.map((option) => CupertinoButton.filled( + child: Text(option.name), + onPressed: option.action == null ? null : () => option.action!(context), + )).map((w) => Padding( + padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), + child: SizedBox( + width: double.infinity, + child: w, + ), + )).toList(), + ), + ), + ), + ); + } +} diff --git a/lib/pages/main/main_page_material.dart b/lib/pages/main/main_page_material.dart new file mode 100644 index 0000000..1a78d76 --- /dev/null +++ b/lib/pages/main/main_page_material.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:info_tren/pages/main/main_page.dart'; + +class MainPageMaterial extends MainPageShared { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(pageTitle), + centerTitle: true, + ), + body: SafeArea( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: options.map((option) => ElevatedButton( + child: Text( + option.name, + style: Theme.of(context).textTheme.button?.copyWith(fontSize: 18), + ), + onPressed: option.action != null ? () => option.action!(context) : null, + )).map((w) => Padding( + padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), + child: SizedBox( + width: double.infinity, + child: w, + ), + )).toList(), + ), + ), + ), + ); + } +} diff --git a/lib/pages/train_info_page/select_train/select_train.dart b/lib/pages/train_info_page/select_train/select_train.dart new file mode 100644 index 0000000..545c6ff --- /dev/null +++ b/lib/pages/train_info_page/select_train/select_train.dart @@ -0,0 +1,61 @@ +import 'dart:io' show Platform; + +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/pages/train_info_page/select_train/select_train_cupertino.dart'; +import 'package:info_tren/pages/train_info_page/select_train/select_train_material.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; +import 'package:info_tren/utils/default_ui_design.dart'; +import 'package:tuple/tuple.dart'; + +typedef TrainSelectedCallback(int trainNumber); + +class SelectTrainPage extends StatefulWidget { + final UiDesign? uiDesign; + + SelectTrainPage({Key? key, this.uiDesign}) : super(key: key); + + static String routeName = "/trainInfo/selectTrain"; + + void onTrainSelected(BuildContext context, int selection) { + Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: selection); + } + + @override + SelectTrainPageState createState() { + final uiDesign = this.uiDesign ?? defaultUiDesign; + switch(uiDesign) { + case UiDesign.MATERIAL: + return SelectTrainPageStateMaterial(); + case UiDesign.CUPERTINO: + return SelectTrainPageStateCupertino(); + default: + throw UnmatchedUiDesignException(uiDesign); + } + } +} + +abstract class SelectTrainPageState extends State { + final String pageTitle = 'Informații despre tren'; + final String textFieldLabel = 'Numărul trenului'; + + TextEditingController trainNoController = TextEditingController(); + + @override + void initState() { + super.initState(); + } + + void onTextChanged() { + setState(() {}); + } + + Widget get suggestionsList => SelectTrainSuggestions( + uiDesign: widget.uiDesign, + userInput: trainNoController.text, + onTrainSelected: (trainNumber) => widget.onTrainSelected(context, trainNumber), + key: ValueKey(trainNoController.text), + ); +} diff --git a/lib/pages/train_info_page/select_train/select_train_cupertino.dart b/lib/pages/train_info_page/select_train/select_train_cupertino.dart new file mode 100644 index 0000000..e9d0ba4 --- /dev/null +++ b/lib/pages/train_info_page/select_train/select_train_cupertino.dart @@ -0,0 +1,39 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; + +class SelectTrainPageStateCupertino extends SelectTrainPageState { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(pageTitle), + ), + child: SafeArea( + bottom: false, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: CupertinoTextField( + controller: trainNoController, + autofocus: true, + placeholder: textFieldLabel, + textInputAction: TextInputAction.search, + keyboardType: TextInputType.number, + onChanged: (_) => onTextChanged(), + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + ), + ), + Expanded( + child: suggestionsList, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/train_info_page/select_train/select_train_material.dart b/lib/pages/train_info_page/select_train/select_train_material.dart new file mode 100644 index 0000000..2edc436 --- /dev/null +++ b/lib/pages/train_info_page/select_train/select_train_material.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; + +class SelectTrainPageStateMaterial extends SelectTrainPageState { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(pageTitle), + centerTitle: true, + ), + body: SafeArea( + bottom: false, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: TextField( + controller: trainNoController, + autofocus: true, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: textFieldLabel, + ), + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + textInputAction: TextInputAction.search, + keyboardType: TextInputType.number, + onChanged: (_) => onTextChanged(), + ), + ), + Expanded( + child: suggestionsList, + ), + ], + ), + ), + ); + } +} diff --git a/lib/train_info_page/train_info_animation_helpers.dart b/lib/pages/train_info_page/train_info_animation_helpers.dart.old similarity index 98% rename from lib/train_info_page/train_info_animation_helpers.dart rename to lib/pages/train_info_page/train_info_animation_helpers.dart.old index dc891a7..7a34ff7 100644 --- a/lib/train_info_page/train_info_animation_helpers.dart +++ b/lib/pages/train_info_page/train_info_animation_helpers.dart.old @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; -import 'package:info_tren/train_info_page/train_info_constants.dart'; +import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; import 'dart:io' show Platform; diff --git a/lib/train_info_page/train_info_constants.dart b/lib/pages/train_info_page/train_info_constants.dart similarity index 100% rename from lib/train_info_page/train_info_constants.dart rename to lib/pages/train_info_page/train_info_constants.dart diff --git a/lib/pages/train_info_page/view_train/train_info.dart b/lib/pages/train_info_page/view_train/train_info.dart new file mode 100644 index 0000000..0ad578f --- /dev/null +++ b/lib/pages/train_info_page/view_train/train_info.dart @@ -0,0 +1,69 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:info_tren/api/train_data.dart'; +import 'package:info_tren/components/loading/loading.dart'; +import 'package:info_tren/components/refresh_future_builder.dart'; +import 'package:info_tren/models/train_data.dart'; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; +import 'package:info_tren/utils/default_ui_design.dart'; + + +class TrainInfo extends StatelessWidget { + static String routeName = "/trainInfo/display"; + + final UiDesign? uiDesign; + final int trainNumber; + + TrainInfo({Key? key, required this.trainNumber, this.uiDesign}): super(key: key); + + @override + Widget build(BuildContext context) { + final uiDesign = this.uiDesign ?? defaultUiDesign; + + return RefreshFutureBuilder( + futureCreator: () => getTrain(trainNumber), + builder: (context, refresh, snapshot) { + + switch (uiDesign) { + case UiDesign.MATERIAL: + if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) { + return TrainInfoLoadingMaterial(title: trainNumber.toString(),); + } + else if (snapshot.state == RefreshFutureBuilderState.error) { + return TrainInfoErrorMaterial(title: '$trainNumber - Error', error: snapshot.error!,); + } + + return TrainInfoMaterial(trainData: snapshot.data!,); + case UiDesign.CUPERTINO: + if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) { + return TrainInfoLoadingCupertino(title: trainNumber.toString(),); + } + else if (snapshot.state == RefreshFutureBuilderState.error) { + return TrainInfoErrorCupertino(title: '$trainNumber - Error', error: snapshot.error!,); + } + + return TrainInfoCupertino(trainData: snapshot.data!,); + default: + throw UnmatchedUiDesignException(uiDesign); + } + }, + ); + } +} + +abstract class TrainInfoLoading extends StatelessWidget { + final String title; + final Widget loadingWidget; + + TrainInfoLoading({required this.title, String? loadingText, UiDesign? uiDesign}) : loadingWidget = Loading(uiDesign: uiDesign, text: loadingText,); +} + +abstract class TrainInfoError extends StatelessWidget { + final String title; + final Object error; + + TrainInfoError({required this.title, required this.error}); +} diff --git a/lib/pages/train_info_page/view_train/train_info_cupertino.dart b/lib/pages/train_info_page/view_train/train_info_cupertino.dart new file mode 100644 index 0000000..4961637 --- /dev/null +++ b/lib/pages/train_info_page/view_train/train_info_cupertino.dart @@ -0,0 +1,741 @@ +import 'package:flutter/cupertino.dart'; +import 'package:info_tren/components/cupertino_divider.dart'; +import 'package:info_tren/models/train_data.dart' hide State; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart'; +import 'package:info_tren/utils/state_to_string.dart'; + +class TrainInfoLoadingCupertino extends TrainInfoLoading { + TrainInfoLoadingCupertino({required String title, String? loadingText}) : super(title: title, loadingText: loadingText, uiDesign: UiDesign.CUPERTINO); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(title), + ), + child: Center( + child: loadingWidget, + ), + ); + } +} + +class TrainInfoErrorCupertino extends TrainInfoError { + TrainInfoErrorCupertino({required Object error, required String title,}) : super(error: error, title: title,); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(title), + ), + child: Center( + child: Text(error.toString()), + ), + ); + } +} + +class TrainInfoCupertino extends StatelessWidget { + final TrainData trainData; + + TrainInfoCupertino({required this.trainData}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text("Informații despre ${trainData.rank} ${trainData.number}"), + ), + child: SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) { + final topPadding = MediaQuery.of(context).padding.top; + + return CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.only( + top: topPadding, + ), + child: Container(), + ), + ), + DisplayTrainID(trainData: trainData,), + DisplayTrainOperator(trainData: trainData,), + DisplayTrainRoute(trainData: trainData,), + DisplayTrainDeparture(trainData: trainData,), + SliverToBoxAdapter( + child: CupertinoDivider( + color: FOREGROUND_WHITE, + ), + ), + DisplayTrainLastInfo(trainData: trainData,), + SliverToBoxAdapter( + child: CupertinoDivider(), + ), + SliverToBoxAdapter( + child: IntrinsicHeight( + child: Row( + children: [ + // Expanded( + // child: DisplayTrainNextStop(trainData: trainData,), + // ), + Expanded( + child: DisplayTrainDestination(trainData: trainData,), + ), + SizedBox( + height: double.infinity, + child: CupertinoVerticalDivider(), + ), + Expanded(child: DisplayTrainRouteDistance(trainData: trainData,),), + ], + ), + ), + ), + // SliverToBoxAdapter( + // child: CupertinoDivider(), + // ), + // SliverToBoxAdapter( + // child: IntrinsicHeight( + // child: Row( + // children: [ + // // Expanded( + // // child: DisplayTrainRouteDuration(trainData: trainData,), + // // ), + // Expanded(child: Container(),), + // SizedBox( + // height: double.infinity, + // child: CupertinoVerticalDivider(), + // ), + // Expanded( + // child: DisplayTrainRouteDistance(trainData: trainData,), + // ) + // ], + // ), + // ), + // ), + SliverToBoxAdapter( + child: CupertinoDivider( + color: FOREGROUND_WHITE, + ), + ), + DisplayTrainStations( + trainData: trainData, + ), + SliverToBoxAdapter( + child: Container( + height: MediaQuery.of(context).viewPadding.bottom, + ), + ), + ], + ); + } + ), + ), + ); + + // return CupertinoPageScaffold( + // navigationBar: CupertinoNavigationBar( + // middle: Text(title ?? ""), + // ), + // child: SafeArea( + // bottom: false, + // child: FutureBuilder( + // future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () { + // Navigator.of(context).pop(); + // }), + // builder: (context, snapshot) { + // if (!snapshot.hasData) { + // return Center( + // child: CupertinoActivityIndicator(), + // ); + // } + + // try { + // Future.wait([ + // snapshot.data.rang, + // snapshot.data.trainNumber + // ]).then((values) { + // setState(() { + // title = "Informații despre ${values[0]} ${values[1]}"; + // }); + // }); + + // return CustomScrollView( + // slivers: [ + // DisplayTrainID(data: snapshot.data,), + // DisplayTrainOperator(data: snapshot.data,), + // DisplayTrainRoute(data: snapshot.data,), + // DisplayTrainDeparture(data: snapshot.data,), + // SliverToBoxAdapter( + // child: CupertinoDivider( + // color: FOREGROUND_WHITE, + // ), + // ), + // DisplayTrainLastInfo(data: snapshot.data,), + // SliverToBoxAdapter( + // child: CupertinoDivider(), + // ), + // SliverToBoxAdapter( + // child: IntrinsicHeight( + // child: Row( + // children: [ + // Expanded( + // child: DisplayTrainNextStop(data: snapshot.data,), + // ), + // SizedBox( + // height: double.infinity, + // child: CupertinoVerticalDivider(), + // ), + // Expanded( + // child: DisplayTrainDestination(data: snapshot.data,), + // ) + // ], + // ), + // ), + // ), + // SliverToBoxAdapter( + // child: CupertinoDivider(), + // ), + // SliverToBoxAdapter( + // child: IntrinsicHeight( + // child: Row( + // children: [ + // Expanded( + // child: DisplayTrainRouteDuration(data: snapshot.data,), + // ), + // SizedBox( + // height: double.infinity, + // child: CupertinoVerticalDivider(), + // ), + // Expanded( + // child: DisplayTrainRouteDistance(data: snapshot.data,), + // ) + // ], + // ), + // ), + // ), + // SliverToBoxAdapter( + // child: CupertinoDivider( + // color: FOREGROUND_WHITE, + // ), + // ), + // DisplayTrainStations( + // data: snapshot.data, + // pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture, + // ), + // ], + // ); + // } + // on OnDemandInvalidatedException { + // Navigator.of(context).pop(); + // print("Got OnDemandInvalidatedException!"); + // return Container(); + // } + // }, + // ), + // ), + // ); + } +} + +class DisplayTrainID extends StatelessWidget { + final TrainData trainData; + DisplayTrainID({required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "${trainData.rank} ${trainData.number}", + style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle, + ), + ), + ), + ); + } +} + +class DisplayTrainRoute extends StatelessWidget { + final TrainData trainData; + + DisplayTrainRoute({required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Row( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.route.from, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + ), + ), + ), + Expanded(child: Container(),), + Center(child: Text("-")), + Expanded(child: Container(),), + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.route.to, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.right, + ), + ), + ), + ], + ), + ); + } +} + +class DisplayTrainOperator extends StatelessWidget { + final TrainData trainData; + + DisplayTrainOperator({required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Center( + child: Text( + trainData.operator, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + fontStyle: FontStyle.italic, + ), + ), + ), + ); + } +} + +class DisplayTrainDeparture extends StatelessWidget { + final TrainData trainData; + + DisplayTrainDeparture({required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(2), + child: Text( + // "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}", + "Plecare în ${trainData.date}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w200, + ), + textAlign: TextAlign.center, + ), + ), + ); + } +} + +class DisplayTrainLastInfo extends StatelessWidget { + final TrainData trainData; + + DisplayTrainLastInfo({required this.trainData}); + + @override + Widget build(BuildContext context) { + if (trainData.status == null) { + return SliverToBoxAdapter(child: Container(),); + } + + return SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Text( + "Ultima informație", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Row( + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.status!.station, + style: CupertinoTheme.of(context).textTheme.textStyle, + textAlign: TextAlign.left, + ), + ), + Expanded(child: Container(),), + Padding( + padding: const EdgeInsets.all(4), + child: Text( + stateToString(trainData.status!.state), + style: CupertinoTheme.of(context).textTheme.textStyle, + textAlign: TextAlign.right, + ), + ), + ], + ), + // FutureDisplay( + // future: trainData.lastInfo.dateAndTime, + // builder: (context, dt) { + // return Text( + // "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}", + // textAlign: TextAlign.center, + // ); + // }, + // ), + Builder( + builder: (context) { + final data = trainData.status!.delay; + + if (data == 0) { + return Container(); + } + + if (data > 0) { + return Text( + "$data minute întârziere", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + color: CupertinoColors.destructiveRed, + ), + ); + } + else { + return Text( + "${-data} minute mai devreme", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 12, + color: CupertinoColors.activeGreen, + ), + ); + } + }, + ) + ], + ), + ); + } +} + +// class DisplayTrainNextStop extends StatelessWidget { +// final TrainData trainData; +// +// DisplayTrainNextStop({required this.trainData}); +// +// @override +// Widget build(BuildContext context) { +// return FutureBuilder( +// future: trainData.nextStop.stationName, +// builder: (context, snapshot) { +// if (!snapshot.hasData) return Container(); +// +// return Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Padding( +// padding: const EdgeInsets.all(4), +// child: Text( +// "Următoarea oprire", +// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( +// fontSize: 20, +// fontWeight: FontWeight.bold, +// ), +// textAlign: TextAlign.center, +// ), +// ), +// CupertinoDivider( +// color: Color.fromRGBO(15, 15, 15, 1), +// ), +// FutureDisplay( +// future: trainData.nextStop.stationName, +// builder: (context, station) { +// return Padding( +// padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), +// child: Text( +// station, +// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( +// fontSize: 18, +// fontWeight: FontWeight.w500, +// ), +// textAlign: TextAlign.center, +// ), +// ); +// }, +// ), +// FutureDisplay( +// future: trainData.nextStop.arrival, +// builder: (context, arrival) { +// const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; +// +// return Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Text( +// "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", +// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( +// fontSize: 14, +// ), +// textAlign: TextAlign.center, +// ), +// Text( +// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", +// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( +// fontSize: 14, +// ), +// textAlign: TextAlign.center, +// ), +// ], +// ); +// }, +// ) +// ], +// ); +// } +// ); +// } +// } + +class DisplayTrainDestination extends StatelessWidget { + final TrainData trainData; + + DisplayTrainDestination({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: Text( + "Destinația", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + CupertinoDivider( + color: Color.fromRGBO(15, 15, 15, 1), + ), + Padding( + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), + child: Text( + trainData.stations.last.name, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ), + Builder( + builder: (context) { + final arrival = trainData.stations.last.arrival!.scheduleTime; + final delay = trainData.stations.last.arrival!.status?.delay ?? 0; + final parts = arrival.split(':'); + final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1])); + final arrivalWithDelay = arrivalDT.add(Duration(minutes: delay)); + final arrivalWithDelayString = '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}'; + // const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Text( + // "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", + // style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + // fontSize: 14, + // ), + // textAlign: TextAlign.center, + // ), + Text.rich( + TextSpan( + text: 'la', + children: [ + TextSpan(text: ' '), + TextSpan( + text: '$arrival', + style: delay == 0 ? null : TextStyle( + decoration: TextDecoration.lineThrough, + ), + ), + if (delay != 0) ...[ + TextSpan(text: ' '), + TextSpan( + text: '$arrivalWithDelayString', + style: TextStyle( + color: delay > 0 ? CupertinoColors.destructiveRed : CupertinoColors.activeGreen, + ), + ), + ] + ], + ), + // "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ); + }, + ) + ], + ); + } +} + +class DisplayTrainRouteDistance extends StatelessWidget { + final TrainData trainData; + + DisplayTrainRouteDistance({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Distanța rutei", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + Text( + "${trainData.stations.last.km} km", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + ], + ); + } +} + +// class DisplayTrainRouteDuration extends StatelessWidget { +// final TrainData trainData; +// +// DisplayTrainRouteDuration({required this.trainData}); +// +// @override +// Widget build(BuildContext context) { +// return Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Text( +// "Durata rutei", +// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( +// fontSize: 18, +// fontWeight: FontWeight.bold, +// ), +// textAlign: TextAlign.center, +// ), +// FutureDisplay( +// future: trainData.routeDuration, +// builder: (context, duration) { +// var durationString = StringBuffer(); +// +// bool firstWritten = false; +// +// if (duration.inDays > 0) { +// firstWritten = true; +// if (duration.inDays == 1) durationString.write("1 zi"); +// else durationString.write("${duration.inDays} zile"); +// duration -= Duration(days: duration.inDays); +// } +// +// if (duration.inHours > 0) { +// if (firstWritten) { +// durationString.write(", "); +// } +// firstWritten = true; +// if (duration.inHours == 1) durationString.write("1 oră"); +// else durationString.write("${duration.inHours} ore"); +// duration -= Duration(hours: duration.inHours); +// } +// +// if (duration.inMinutes > 0) { +// if (firstWritten) { +// durationString.write(", "); +// } +// firstWritten = true; +// if (duration.inMinutes == 1) durationString.write("1 minut"); +// else durationString.write("${duration.inMinutes} minute"); +// duration -= Duration(minutes: duration.inMinutes); +// } +// +// return Text( +// durationString.toString(), +// style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( +// fontSize: 16, +// ), +// textAlign: TextAlign.center, +// ); +// }, +// ), +// ], +// ); +// } +// } + +class DisplayTrainStations extends StatelessWidget { + final TrainData trainData; + + DisplayTrainStations({required this.trainData,}); + + @override + Widget build(BuildContext context) { + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index.isOdd) { + return CupertinoDivider(); + } + else { + final itemIndex = index ~/ 2; + return IndexedSemantics( + child: DisplayTrainStation( + station: trainData.stations[itemIndex], + ), + index: itemIndex, + ); + } + }, + childCount: trainData.stations.length * 2 - 1, + addSemanticIndexes: false, + ), + ); + } +} diff --git a/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart new file mode 100644 index 0000000..864cc30 --- /dev/null +++ b/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart @@ -0,0 +1,466 @@ +import 'package:flutter/cupertino.dart'; +import 'package:info_tren/models/train_data.dart'; +import 'package:info_tren/pages/train_info_page/train_info_constants.dart'; + +class DisplayTrainStation extends StatelessWidget { + final Station station; + + DisplayTrainStation({required this.station}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Builder( + builder: (context) { + final delay = station.departure?.status?.delay ?? station.arrival?.status?.delay; + final real = station.departure?.status?.real ?? station.arrival?.status?.real; + + final isDelayed = delay != null && delay > 0 && real == true; + final isOnTime = delay != null && delay <= 0 && real == true; + final isNotScheduled = false; + + return KmBadge( + station: station, + isNotScheduled: isNotScheduled, + isDelayed: isDelayed, + isOnTime: isOnTime, + ); + } + ), + Expanded( + child: Title( + station: station, + ), + ) + ], + ), + Time( + station: station, + ), + Delay( + station: station, + ), + ], + ); + } +} + +class KmBadge extends StatelessWidget { + final Station station; + final bool isNotScheduled; + final bool isOnTime; + final bool isDelayed; + + KmBadge({ + required this.station, + this.isNotScheduled = false, + this.isOnTime = false, + this.isDelayed = false, + }); + + @override + Widget build(BuildContext context) { + Color foregroundColor = FOREGROUND_WHITE; + Color? backgroundColor; + + if (isNotScheduled) { + foregroundColor = Color.fromRGBO(225, 175, 30, 1); + backgroundColor = Color.fromRGBO(80, 40, 10, 1); + } + else if (isOnTime) { + foregroundColor = Color.fromRGBO(130, 175, 65, 1); + backgroundColor = Color.fromRGBO(40, 80, 10, 1); + } + else if (isDelayed) { + foregroundColor = Color.fromRGBO(225, 75, 30, 1); + backgroundColor = Color.fromRGBO(80, 20, 10, 1); + } + + return Padding( + padding: const EdgeInsets.all(8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + width: 2, + color: foregroundColor, + ), + color: backgroundColor, + // color: CupertinoColors.activeOrange, + ), + width: 48, + height: 48, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Center( + child: Text( + station.km.toString(), + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 20, + fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, + color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, + ), + textAlign: TextAlign.center, + ), + ), + ), + Text( + "km", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 10, + color: MediaQuery.of(context).boldText ? FOREGROUND_WHITE : foregroundColor, + ), + ), + ], + ), + ), + ); + } +} + +class Title extends StatelessWidget { + final Station station; + + Title({ + required this.station + }); + + @override + Widget build(BuildContext context) { + return Text( + station.name, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + fontWeight: MediaQuery.of(context).boldText ? FontWeight.w500 : FontWeight.w300, + // fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, + ), + textAlign: TextAlign.center, + ); + } +} + +class Time extends StatelessWidget { + final Station station; + + Time({ + required this.station, + }); + + @override + Widget build(BuildContext context) { + if (station.arrival == null) { + // Plecare + return DepartureTime( + station: station, + firstStation: true, + ); + } + + if (station.departure == null) { + // Sosire + return ArrivalTime( + station: station, + finalStation: true, + ); + } + + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "→", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + ), + ), + Container(width: 2,), + ArrivalTime(station: station,), + Expanded(child: Container(),), + StopTime(station: station,), + Expanded(child: Container(),), + DepartureTime(station: station,), + Container(width: 2,), + Text( + "→", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + ), + ), + ], + ); + } +} + +class ArrivalTime extends StatelessWidget { + final Station station; + final bool finalStation; + + ArrivalTime({ + required this.station, + this.finalStation = false, + }); + + @override + Widget build(BuildContext context) { + if (finalStation) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "→", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + ), + ), + Container(width: 2,), + Text("sosire la "), + ArrivalTime(station: station,), + Expanded(child: Container(),), + ], + ); + } + else { + final delay = station.arrival!.status?.delay ?? 0; + final time = station.arrival!.scheduleTime; + + if (delay == 0) { + return Text("$time"); + } + else if (delay > 0) { + final splits = time.split(":").map((s) => int.parse(s)).toList(); + + final now = DateTime.now(); + final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final newDate = oldDate.add(Duration(minutes: delay)); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + decoration: TextDecoration.lineThrough, + ), + ), + Text( + "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + color: CupertinoColors.destructiveRed, + ), + ), + ], + ); + } + else { + final splits = time.split(":").map((s) => int.parse(s)).toList(); + + final now = DateTime.now(); + final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final newDate = oldDate.subtract(Duration(minutes: delay)); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + decoration: TextDecoration.lineThrough, + ), + ), + Text( + "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + color: CupertinoColors.activeGreen, + ), + ), + ], + ); + } + } + } +} + +class StopTime extends StatelessWidget { + final Station station; + + StopTime({ + required this.station, + }); + + @override + Widget build(BuildContext context) { + final stopsFor = station.stoppingTime!; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "staționează pentru", + textAlign: TextAlign.center, + ), + Builder( + builder: (context) { + int stopsForInt = stopsFor; + if (stopsForInt == 1) { + return Text( + "1 minut", + textAlign: TextAlign.center, + ); + } + else if (stopsForInt < 20) { + return Text( + "$stopsFor minute", + textAlign: TextAlign.center, + ); + } + else { + return Text( + "$stopsFor de minute", + textAlign: TextAlign.center, + ); + } + }, + ) + ], + ); + } +} + +class DepartureTime extends StatelessWidget { + final Station station; + final bool firstStation; + + DepartureTime({ + required this.station, + this.firstStation = false, + }); + + @override + Widget build(BuildContext context) { + if (firstStation) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded(child: Container(),), + Text("plecare la "), + DepartureTime(station: station,), + Container(width: 2,), + Text( + "→", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + ), + ), + ], + ); + } + else { + final delay = station.departure!.status?.delay ?? 0; + final time = station.departure!.scheduleTime; + + if (delay == 0) { + return Text("$time"); + } + else if (delay > 0) { + final splits = time.split(":").map((s) => int.parse(s)).toList(); + + final now = DateTime.now(); + final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final newDate = oldDate.add(Duration(minutes: delay)); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + decoration: TextDecoration.lineThrough, + ), + ), + Text( + "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + color: CupertinoColors.destructiveRed, + ), + ), + ], + ); + } + else { + final splits = time.split(":").map((s) => int.parse(s)).toList(); + + final now = DateTime.now(); + final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final newDate = oldDate.subtract(Duration(minutes: delay)); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + decoration: TextDecoration.lineThrough, + ), + ), + Text( + "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + color: CupertinoColors.activeGreen, + ), + ), + ], + ); + } + } + } +} + +class Delay extends StatelessWidget { + final Station station; + + Delay({ + required this.station, + }); + + @override + Widget build(BuildContext context) { + if (station.arrival?.status == null && station.departure?.status == null) { + return Container(); + } + var delay = station.arrival?.status?.delay; + if (station.departure?.status?.real == true) { + delay = station.departure?.status?.delay; + } + + if (delay == 0 || delay == null) return Container(); + else if (delay > 0) { + return Text( + "$delay minute întârziere", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + color: CupertinoColors.destructiveRed, + fontSize: 14, + fontStyle: FontStyle.italic, + ), + ); + } + else if (delay < 0) { + return Text( + "${-delay} minute mai devreme", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + color: CupertinoColors.activeGreen, + fontSize: 14, + fontStyle: FontStyle.italic, + ), + ); + } + + return Container(); + } +} diff --git a/lib/pages/train_info_page/view_train/train_info_material.dart b/lib/pages/train_info_page/view_train/train_info_material.dart new file mode 100644 index 0000000..8b2060d --- /dev/null +++ b/lib/pages/train_info_page/view_train/train_info_material.dart @@ -0,0 +1,659 @@ +import 'package:flutter/material.dart'; +import 'package:info_tren/components/slim_app_bar.dart'; +import 'package:info_tren/models/train_data.dart' hide State; +import 'package:info_tren/models/ui_design.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart'; +import 'package:info_tren/utils/state_to_string.dart'; + +class TrainInfoLoadingMaterial extends TrainInfoLoading { + TrainInfoLoadingMaterial({required String title, String? loadingText}) : super(title: title, loadingText: loadingText, uiDesign: UiDesign.MATERIAL); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(title), + ), + body: Center( + child: loadingWidget, + ), + ); + } +} + +class TrainInfoErrorMaterial extends TrainInfoError { + TrainInfoErrorMaterial({required Object error, required String title,}) : super(error: error, title: title,); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(title), + ), + body: Center( + child: Text(error.toString()), + ), + ); + } +} + +bool isSmallScreen(BuildContext context) => MediaQuery.of(context).size.height <= 425; + +class TrainInfoMaterial extends StatelessWidget { + final TrainData trainData; + + TrainInfoMaterial({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Builder( + builder: (context) { + return Scaffold( + appBar: isSmallScreen(context) ? null : AppBar( + centerTitle: true, + title: Text("Informații despre ${trainData.rank} ${trainData.number}"), + ), + body: Column( + children: [ + if (isSmallScreen(context)) + SlimAppBar( + title: 'INFO TREN - ${trainData.rank} ${trainData.number}' + ), + Expanded( + child: SafeArea( + bottom: false, + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: DisplayTrainID(trainData: trainData,), + ), + SliverToBoxAdapter( + child: DisplayTrainOperator(trainData: trainData,), + ), + SliverPadding( + padding: const EdgeInsets.only(left: 2, right: 2), + sliver: SliverToBoxAdapter( + child: DisplayTrainRoute(trainData: trainData,), + ), + ), + SliverToBoxAdapter( + child: DisplayTrainDeparture(trainData: trainData,), + ), + // SliverToBoxAdapter( + // child: Divider( + // color: Colors.white70, + // height: isSmallScreen(context) ? 8 : 16, + // ), + // ), + SliverToBoxAdapter( + child: DisplayTrainLastInfo(trainData: trainData,), + ), + SliverToBoxAdapter( + child: IntrinsicHeight( + child: Row( + children: [ + // Expanded(child: DisplayTrainNextStop(trainData: trainData,)), + Expanded(child: DisplayTrainDestination(trainData: trainData,)), + Expanded(child: DisplayTrainRouteDistance(trainData: trainData,),), + ], + ), + ), + ), + // SliverToBoxAdapter( + // child: IntrinsicHeight( + // child: Row( + // children: [ + // // Expanded(child: DisplayTrainRouteDuration(trainData: trainData,)), + // Expanded(child: Container(),), + // Expanded(child: DisplayTrainRouteDistance(trainData: trainData,)), + // ], + // ), + // ), + // ), + SliverToBoxAdapter( + child: Divider( + color: Colors.white70, + height: isSmallScreen(context) ? 8 : 16, + ), + ), + DisplayTrainStations( + trainData: trainData, + ), + SliverToBoxAdapter( + child: Container( + height: MediaQuery.of(context).viewPadding.bottom, + ), + ), + ], + ), + ), + ), + ], + ), + ); + }, + ); + } +} + +class DisplayTrainID extends StatelessWidget { + final TrainData trainData; + + DisplayTrainID({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Text( + "${trainData.rank} ${trainData.number}", + style: (isSmallScreen(context) + ? Theme.of(context).textTheme.headline4 + : Theme.of(context).textTheme.headline3)?.copyWith( + color: Theme.of(context).textTheme.bodyText2?.color, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ); + } +} + +class DisplayTrainOperator extends StatelessWidget { + final TrainData trainData; + + DisplayTrainOperator({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Text( + trainData.operator, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontStyle: FontStyle.italic, + fontSize: isSmallScreen(context) ? 12 : 14, + ), + textAlign: TextAlign.center, + ); + } +} + +class DisplayTrainRoute extends StatelessWidget { + final TrainData trainData; + + DisplayTrainRoute({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.route.from, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: 16, + ), + ), + ), + ), + Expanded(child: Container(),), + Center(child: Text("-")), + Expanded(child: Container(),), + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.route.to, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.right, + ), + ), + ), + ], + ); + } +} + +class DisplayTrainDeparture extends StatelessWidget { + final TrainData trainData; + + DisplayTrainDeparture({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(2), + child: Text( + // "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}", + "Plecare în ${trainData.date}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w200, + fontSize: isSmallScreen(context) ? 14 : 16, + ), + textAlign: TextAlign.center, + ), + ); + } +} + +class DisplayTrainLastInfo extends StatelessWidget { + final TrainData trainData; + + DisplayTrainLastInfo({required this.trainData}); + + @override + Widget build(BuildContext context) { + if (trainData.status == null) { + return Container(); + } + + return Card( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Text( + "Ultima informație", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 20 : 22, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Row( + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: Text( + trainData.status!.station, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 16 : 18, + ), + textAlign: TextAlign.left, + ), + ), + Expanded(child: Container(),), + Padding( + padding: const EdgeInsets.all(4), + child: Text( + stateToString(trainData.status!.state), + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 16 : 18, + ), + textAlign: TextAlign.right, + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(2), + child: Row( + children: [ + // FutureDisplay( + // future: trainData.lastInfo.dateAndTime, + // builder: (context, dt) { + // return Text( + // "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}", + // textAlign: TextAlign.center, + // ); + // }, + // ), + Expanded(child: Container(),), + Builder( + builder: (context) { + final data = trainData.status!.delay; + if (data == 0) { + return Container(); + } + + if (data > 0) { + return Text( + "$data minute întârziere", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 14 : 16, + color: Colors.red.shade300, + ), + ); + } + else { + return Text( + "${-data} minute mai devreme", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 14 : 16, + color: Colors.green.shade300, + ), + ); + } + }, + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +// class DisplayTrainNextStop extends StatelessWidget { +// final OnDemandTrainData trainData; +// +// DisplayTrainNextStop({@required this.trainData}); +// +// @override +// Widget build(BuildContext context) { +// return FutureBuilder( +// future: trainData.nextStop.stationName, +// builder: (context, snapshot) { +// if (!snapshot.hasData) return Container(height: 0,); +// +// return Card( +// child: Center( +// child: Padding( +// padding: const EdgeInsets.all(2), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Padding( +// padding: const EdgeInsets.all(4), +// child: Text( +// "Următoarea oprire", +// style: Theme.of(context).textTheme.bodyText2.copyWith( +// fontSize: isSmallScreen(context) ? 18 : 20, +// fontWeight: FontWeight.bold, +// ), +// textAlign: TextAlign.center, +// ), +// ), +// FutureDisplay( +// future: trainData.nextStop.stationName, +// builder: (context, station) { +// return Padding( +// padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), +// child: Text( +// station, +// style: Theme.of(context).textTheme.bodyText2.copyWith( +// fontSize: isSmallScreen(context) ? 16 : 18, +// fontWeight: FontWeight.w500, +// ), +// textAlign: TextAlign.center, +// ), +// ); +// }, +// ), +// FutureDisplay( +// future: trainData.nextStop.arrival, +// builder: (context, arrival) { +// const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; +// +// return Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Text( +// "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", +// style: Theme.of(context).textTheme.bodyText2.copyWith( +// fontSize: isSmallScreen(context) ? 12 : 14, +// ), +// textAlign: TextAlign.center, +// ), +// Text( +// "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", +// style: Theme.of(context).textTheme.bodyText2.copyWith( +// fontSize: isSmallScreen(context) ? 12 : 14, +// ), +// textAlign: TextAlign.center, +// ), +// ], +// ); +// }, +// ) +// ], +// ), +// ), +// ), +// ); +// } +// ); +// } +// } + +class DisplayTrainDestination extends StatelessWidget { + final TrainData trainData; + + DisplayTrainDestination({required this.trainData}); + + @override + Widget build(BuildContext context) { + final destination = trainData.stations.last; + + return Card( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: Text( + "Destinația", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 20 : 22, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), + child: Text( + destination.name, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 18 : 20, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ), + Builder( + builder: (context) { + final arrival = destination.arrival!.scheduleTime; + final delay = trainData.stations.last.arrival!.status?.delay ?? 0; + final parts = arrival.split(':'); + final arrivalDT = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, int.parse(parts[0]), int.parse(parts[1])); + final arrivalWithDelay = arrivalDT.add(Duration(minutes: delay)); + final arrivalWithDelayString = '${arrivalWithDelay.hour}:${arrivalWithDelay.minute.toString().padLeft(2, "0")}'; + // const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Text( + // "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", + // style: Theme.of(context).textTheme.bodyText2?.copyWith( + // fontSize: isSmallScreen(context) ? 12 : 14, + // ), + // textAlign: TextAlign.center, + // ), + Text.rich( + // "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", + TextSpan( + text: 'la', + children: [ + TextSpan(text: ' '), + TextSpan( + text: '$arrival', + style: delay == 0 ? null : TextStyle( + decoration: TextDecoration.lineThrough, + ), + ), + if (delay != 0) ...[ + TextSpan(text: ' '), + TextSpan( + text: '$arrivalWithDelayString', + style: TextStyle( + color: delay > 0 ? Colors.red.shade300 : Colors.green.shade300, + ), + ), + ] + ], + ), + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 14 : 16, + ), + textAlign: TextAlign.center, + ), + ], + ); + }, + ) + ], + ), + ), + ), + ); + } +} + +class DisplayTrainRouteDistance extends StatelessWidget { + final TrainData trainData; + + DisplayTrainRouteDistance({required this.trainData}); + + @override + Widget build(BuildContext context) { + return Card( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Distanța rutei", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 20 : 22, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + Text( + "${trainData.stations.last.km} km", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 18 : 20, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ); + } +} + +// class DisplayTrainRouteDuration extends StatelessWidget { +// final TrainData trainData; +// +// DisplayTrainRouteDuration({required this.trainData}); +// +// @override +// Widget build(BuildContext context) { +// return Card( +// child: Center( +// child: Padding( +// padding: const EdgeInsets.all(2), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Text( +// "Durata rutei", +// style: Theme.of(context).textTheme.bodyText2?.copyWith( +// fontSize: isSmallScreen(context) ? 16 : 18, +// fontWeight: FontWeight.bold, +// ), +// textAlign: TextAlign.center, +// ), +// FutureDisplay( +// future: trainData.routeDuration, +// builder: (context, duration) { +// var durationString = StringBuffer(); +// +// bool firstWritten = false; +// +// if (duration.inDays > 0) { +// firstWritten = true; +// if (duration.inDays == 1) durationString.write("1 zi"); +// else durationString.write("${duration.inDays} zile"); +// duration -= Duration(days: duration.inDays); +// } +// +// if (duration.inHours > 0) { +// if (firstWritten) { +// durationString.write(", "); +// } +// firstWritten = true; +// if (duration.inHours == 1) durationString.write("1 oră"); +// else durationString.write("${duration.inHours} ore"); +// duration -= Duration(hours: duration.inHours); +// } +// +// if (duration.inMinutes > 0) { +// if (firstWritten) { +// durationString.write(", "); +// } +// firstWritten = true; +// if (duration.inMinutes == 1) durationString.write("1 minut"); +// else durationString.write("${duration.inMinutes} minute"); +// duration -= Duration(minutes: duration.inMinutes); +// } +// +// return Text( +// durationString.toString(), +// style: Theme.of(context).textTheme.bodyText2?.copyWith( +// fontSize: isSmallScreen(context) ? 14 : 16, +// ), +// textAlign: TextAlign.center, +// ); +// }, +// ), +// ], +// ), +// ), +// ), +// ); +// } +// } + +class DisplayTrainStations extends StatelessWidget { + final TrainData trainData; + + DisplayTrainStations({required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return IndexedSemantics( + child: DisplayTrainStation( + station: trainData.stations[index], + ), + index: index, + ); + }, + childCount: trainData.stations.length, + addSemanticIndexes: true, + ), + ); + } +} + diff --git a/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart new file mode 100644 index 0000000..01f6ac4 --- /dev/null +++ b/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart @@ -0,0 +1,476 @@ +import 'package:flutter/material.dart'; +import 'package:info_tren/models/train_data.dart'; +import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart' show isSmallScreen; + +class DisplayTrainStation extends StatelessWidget { + final Station station; + + DisplayTrainStation({required this.station}); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Builder( + builder: (context) { + final delay = station.departure?.status?.delay ?? station.arrival?.status?.delay; + final real = station.departure?.status?.real ?? station.arrival?.status?.real; + + final isDelayed = delay != null && delay > 0 && real == true; + final isOnTime = delay != null && delay <= 0 && real == true; + final isNotScheduled = false; + + return KmBadge( + station: station, + isNotScheduled: isNotScheduled, + isDelayed: isDelayed, + isOnTime: isOnTime, + ); + } + ), + Expanded( + child: Title( + station: station, + ), + ), + ], + ), + Time( + station: station, + ), + Delay( + station: station, + ), + ], + ), + ), + ); + } +} + +class KmBadge extends StatelessWidget { + final Station station; + final bool isNotScheduled; + final bool isOnTime; + final bool isDelayed; + + KmBadge({ + required this.station, + this.isNotScheduled = false, + this.isOnTime = false, + this.isDelayed = false, + }); + + @override + Widget build(BuildContext context) { + Color foregroundColor = Colors.white70; + Color? backgroundColor; + + if (isNotScheduled) { + foregroundColor = Colors.orange.shade300; + backgroundColor = Colors.orange.shade900.withOpacity(0.3); + } + else if (isOnTime) { + foregroundColor = Colors.green.shade300; + backgroundColor = Colors.green.shade900.withOpacity(0.3); + } + else if (isDelayed) { + foregroundColor = Colors.red.shade300; + backgroundColor = Colors.red.shade900.withOpacity(0.3); + } + + return Padding( + padding: const EdgeInsets.all(8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + width: 2, + color: foregroundColor, + ), + color: backgroundColor, + ), + width: isSmallScreen(context) ? 42 : 48, + height: isSmallScreen(context) ? 42 : 48, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Center( + child: Text( + station.km.toString(), + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 16 : 20, + fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200, + color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, + ), + textAlign: TextAlign.center, + ), + ), + ), + Text( + "km", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: 10, + color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor, + ), + ), + ], + ), + ), + ); + } +} + +class Title extends StatelessWidget { + final Station station; + + Title({ + required this.station + }); + + @override + Widget build(BuildContext context) { + return Text( + station.name, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + fontWeight: MediaQuery.of(context).boldText ? FontWeight.w500 : FontWeight.w300, + // fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, + ), + textAlign: TextAlign.center, + ); + } +} + +class Time extends StatelessWidget { + final Station station; + + Time({ + required this.station, + }); + + @override + Widget build(BuildContext context) { + if (station.arrival == null) { + // Plecare + return DepartureTime( + station: station, + firstStation: true, + ); + } + + if (station.departure == null) { + // Sosire + return ArrivalTime( + station: station, + finalStation: true, + ); + } + + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "→", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + ), + ), + Container(width: 2,), + ArrivalTime(station: station,), + Expanded(child: Container(),), + StopTime(station: station,), + Expanded(child: Container(),), + DepartureTime(station: station,), + Container(width: 2,), + Text( + "→", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + ), + ), + ], + ); + } +} + +class ArrivalTime extends StatelessWidget { + final Station station; + final bool finalStation; + + ArrivalTime({ + required this.station, + this.finalStation = false, + }); + + @override + Widget build(BuildContext context) { + if (station.arrival == null) { + return Container(); + } + if (finalStation) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "→", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + ), + ), + Container(width: 2,), + Text("sosire la "), + ArrivalTime(station: station,), + Expanded(child: Container(),), + ], + ); + } + else { + final delay = station.arrival!.status?.delay ?? 0; + final time = station.arrival!.scheduleTime; + + if (delay == 0) { + return Text("$time"); + } + else if (delay > 0) { + final splits = time.split(":").map((s) => int.parse(s)).toList(); + + final now = DateTime.now(); + final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final newDate = oldDate.add(Duration(minutes: delay)); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + decoration: TextDecoration.lineThrough, + ), + ), + Text( + "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + color: Colors.red.shade300, + ), + ), + ], + ); + } + else { + final splits = time.split(":").map((s) => int.parse(s)).toList(); + + final now = DateTime.now(); + final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final newDate = oldDate.add(Duration(minutes: delay)); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + decoration: TextDecoration.lineThrough, + ), + ), + Text( + "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + color: Colors.green.shade300, + ), + ), + ], + ); + } + } + } +} + +class StopTime extends StatelessWidget { + final Station station; + + StopTime({ + required this.station, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "staționează pentru", + textAlign: TextAlign.center, + ), + Builder( + builder: (context) { + int stopsForInt = station.stoppingTime!; + if (stopsForInt == 1) { + return Text( + "1 minut", + textAlign: TextAlign.center, + ); + } + else if (stopsForInt < 20) { + return Text( + "${station.stoppingTime} minute", + textAlign: TextAlign.center, + ); + } + else { + return Text( + "${station.stoppingTime} de minute", + textAlign: TextAlign.center, + ); + } + }, + ) + ], + ); + } +} + +class DepartureTime extends StatelessWidget { + final Station station; + final bool firstStation; + + DepartureTime({ + required this.station, + this.firstStation = false, + }); + + @override + Widget build(BuildContext context) { + if (station.departure == null) { + return Container(); + } + if (firstStation) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded(child: Container(),), + Text("plecare la "), + DepartureTime(station: station,), + Container(width: 2,), + Text( + "→", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + fontSize: 22, + ), + ), + ], + ); + } + else { + final delay = station.departure!.status?.delay ?? 0; + final time = station.departure!.scheduleTime; + + if (delay == 0) { + return Text("$time"); + } + else if (delay > 0) { + final splits = time.split(":").map((s) => int.parse(s)).toList(); + + final now = DateTime.now(); + final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final newDate = oldDate.add(Duration(minutes: delay)); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + decoration: TextDecoration.lineThrough, + ), + ), + Text( + "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + color: Colors.red.shade300, + ), + ), + ], + ); + } + else { + final splits = time.split(":").map((s) => int.parse(s)).toList(); + + final now = DateTime.now(); + final oldDate = DateTime(now.year, now.month, now.day, splits[0], splits[1]); + final newDate = oldDate.add(Duration(minutes: delay)); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + decoration: TextDecoration.lineThrough, + ), + ), + Text( + "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + color: Colors.green.shade300, + ), + ), + ], + ); + } + } + } +} + + +class Delay extends StatelessWidget { + final Station station; + + Delay({ + required this.station, + }); + + @override + Widget build(BuildContext context) { + if (station.arrival?.status == null && station.departure?.status == null) { + return Container(); + } + var delay = station.arrival?.status?.delay; + if (station.departure?.status?.real == true) { + delay = station.departure?.status?.delay; + } + + if (delay == 0 || delay == null) return Container(); + else if (delay > 0) { + return Text( + "$delay minute întârziere", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + color: Colors.red.shade300, + fontSize: 14, + fontStyle: FontStyle.italic, + ), + ); + } + else if (delay < 0) { + return Text( + "${-delay} minute mai devreme", + style: Theme.of(context).textTheme.bodyText2?.copyWith( + color: Colors.green.shade300, + fontSize: 14, + fontStyle: FontStyle.italic, + ), + ); + } + + return Container(); + } +} diff --git a/lib/stations_list.dart b/lib/stations_list.dart.old similarity index 78% rename from lib/stations_list.dart rename to lib/stations_list.dart.old index a69964d..6f8129f 100644 --- a/lib/stations_list.dart +++ b/lib/stations_list.dart.old @@ -40,7 +40,7 @@ enum NotchStyle { } class StopListLine extends StatelessWidget { - final StationEntry station; + final Station station; final int width; StopListLine(this.station, {this.width = 32}) : assert(width.isEven); @@ -151,7 +151,7 @@ class StopListLinePainter extends CustomPainter { } class StopOnLineDetails extends StatelessWidget { - final StationEntry station; + final Station station; StopOnLineDetails(this.station); @override @@ -172,11 +172,11 @@ class StopOnLineDetails extends StatelessWidget { textAlign: TextAlign.center, ), ), - if (station.observations == "ONI") - Padding( - padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.5), - child: Text("oprire ne-itinerarică", style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700),), - ), + // if (station.observations == "ONI") + // Padding( + // padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.5), + // child: Text("oprire ne-itinerarică", style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700),), + // ), Padding( padding: const EdgeInsets.fromLTRB(8.0, 0.5, 8.0, 8.0), child: Text( @@ -186,11 +186,12 @@ class StopOnLineDetails extends StatelessWidget { ), ), StopOnLineTimeDetails(station), - if (station.real) - Padding( - padding: const EdgeInsets.all(2.0), - child: StopOnLineDelayDetails(station), - ), + // TODO: Figure out how to display delay info + // if (station.arrival != null && station.arrival.status != null || station.departure != null && station.departure.status != null) + // Padding( + // padding: const EdgeInsets.all(2.0), + // child: StopOnLineDelayDetails(station), + // ), Divider( height: 0, ), @@ -201,14 +202,14 @@ class StopOnLineDetails extends StatelessWidget { } class StopOnLineTimeDetails extends StatelessWidget { - final StationEntry station; + final Station station; StopOnLineTimeDetails(this.station); @override Widget build(BuildContext context) { return Row( children: [ - if (station.arrivalTime.isNotEmpty) + if (station.arrival != null) Padding( padding: const EdgeInsets.all(4.0), child: Align( @@ -221,14 +222,14 @@ class StopOnLineTimeDetails extends StatelessWidget { textAlign: TextAlign.left, ), Text( - station.arrivalTime, + station.arrival.scheduleTime, textAlign: TextAlign.left, ) ], ), ), ), - if (station.waitTime.isNotEmpty) + if (station.stoppingTime != null) Expanded( child: Padding( padding: const EdgeInsets.all(4.0), @@ -242,7 +243,7 @@ class StopOnLineTimeDetails extends StatelessWidget { textAlign: TextAlign.center, ), Text( - "${station.waitTime} ${station.waitTime == "1" ? "minut" : "minute"}", + "${station.stoppingTime} ${station.stoppingTime == 1 ? "minut" : "minute"}", textAlign: TextAlign.center, ) ], @@ -252,7 +253,7 @@ class StopOnLineTimeDetails extends StatelessWidget { ) else Expanded(child: Container(),), - if (station.departureTime.isNotEmpty) + if (station.departure != null) Padding( padding: const EdgeInsets.all(4.0), child: Align( @@ -265,7 +266,7 @@ class StopOnLineTimeDetails extends StatelessWidget { textAlign: TextAlign.right, ), Text( - station.departureTime, + station.departure.scheduleTime, textAlign: TextAlign.right, ) ], @@ -277,32 +278,32 @@ class StopOnLineTimeDetails extends StatelessWidget { } } -class StopOnLineDelayDetails extends StatelessWidget { - final StationEntry station; - StopOnLineDelayDetails(this.station); +// class StopOnLineDelayDetails extends StatelessWidget { +// final Station station; +// StopOnLineDelayDetails(this.station); - @override - Widget build(BuildContext context) { - if (station.delay == 0) { - return Text( - "Fără întârziere", - style: Theme.of(context).textTheme.caption, - textAlign: TextAlign.center, - ); - } - else if (station.delay < 0) { - return Text( - "${-(station.delay)} minute mai devreme", - style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.green.shade700), - textAlign: TextAlign.center, - ); - } - else { - return Text( - "${station.delay} minute întârziere", - style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700), - textAlign: TextAlign.center, - ); - } - } -} \ No newline at end of file +// @override +// Widget build(BuildContext context) { +// if (station.delay == 0) { +// return Text( +// "Fără întârziere", +// style: Theme.of(context).textTheme.caption, +// textAlign: TextAlign.center, +// ); +// } +// else if (station.delay < 0) { +// return Text( +// "${-(station.delay)} minute mai devreme", +// style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.green.shade700), +// textAlign: TextAlign.center, +// ); +// } +// else { +// return Text( +// "${station.delay} minute întârziere", +// style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700), +// textAlign: TextAlign.center, +// ); +// } +// } +// } \ No newline at end of file diff --git a/lib/train_info_display.dart b/lib/train_info_display.dart index 6f298dc..abf88fb 100644 --- a/lib/train_info_display.dart +++ b/lib/train_info_display.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:info_tren/stations_list.dart'; +import 'package:info_tren/stations_list.dart.old'; import 'models/train_data.dart'; @@ -29,33 +29,30 @@ class TrainInfoDisplayData extends StatelessWidget { padding: const EdgeInsets.all(4.0), child: TotalDetails(trainData), ), - if (trainData.destination.station.isNotEmpty) - ...[ - CustomDivider(), - Padding( - padding: const EdgeInsets.all(4.0), - child: Destination(trainData), - ), - ], + CustomDivider(), + Padding( + padding: const EdgeInsets.all(4.0), + child: Destination(trainData), + ), CustomDivider(), Padding( padding: const EdgeInsets.all(4.0), child: LastUpdate(trainData), ), - if (trainData.nextStop.station.isNotEmpty) - ...[ - CustomDivider(), - Padding( - padding: const EdgeInsets.all(4.0), - child: NextStop(trainData), - ), - ], + // if (trainData.nextStop.station.isNotEmpty) + // ...[ + // CustomDivider(), + // Padding( + // padding: const EdgeInsets.all(4.0), + // child: NextStop(trainData), + // ), + // ], CustomDivider(), Padding( padding: const EdgeInsets.all(4.0), child: TrainStatus(trainData), ), - Divider(color: Theme.of(context).accentColor,), + Divider(color: Theme.of(context).colorScheme.secondary,), Padding( padding: const EdgeInsets.all(4.0), child: StationsList(trainData), @@ -81,7 +78,7 @@ class TrainName extends StatelessWidget { @override Widget build(BuildContext context) { return Text( - "${trainData.rang} ${trainData.trainNumber}", + "${trainData.rank} ${trainData.number}", style: Theme.of(context).textTheme.headline3, ); } @@ -98,20 +95,20 @@ class TrainRoute extends StatelessWidget { children: [ Expanded( child: Text( - "${trainData.route.split("-")[0]}", - style: Theme.of(context).textTheme.bodyText1.copyWith(fontStyle: FontStyle.italic), + trainData.route.from, + style: Theme.of(context).textTheme.bodyText1?.copyWith(fontStyle: FontStyle.italic), textAlign: TextAlign.left, ), ), Text( "-", - style: Theme.of(context).textTheme.bodyText1.copyWith(fontStyle: FontStyle.italic), + style: Theme.of(context).textTheme.bodyText1?.copyWith(fontStyle: FontStyle.italic), textAlign: TextAlign.center, ), Expanded( child: Text( - "${trainData.route.split("-")[1]}", - style: Theme.of(context).textTheme.bodyText1.copyWith(fontStyle: FontStyle.italic), + trainData.route.to, + style: Theme.of(context).textTheme.bodyText1?.copyWith(fontStyle: FontStyle.italic), textAlign: TextAlign.right, ), ), @@ -141,7 +138,7 @@ class TrainStatus extends StatelessWidget { @override Widget build(BuildContext context) { return Text( - trainData.state, + trainData.status.toString(), textAlign: TextAlign.center, style: Theme.of(context).textTheme.headline5, ); @@ -154,16 +151,16 @@ class Destination extends StatelessWidget { @override Widget build(BuildContext context) { - if (trainData.destination.station.isEmpty) return Container(); + final destinationStation = trainData.stations.last; return Column( children: [ Text( - "Destinația: ${trainData.destination.station}", + "Destinația: ${destinationStation.name}", textAlign: TextAlign.center, ), Text( - "Sosește în ${trainData.destination.dateAndTime.split(" ")[0]} la ${trainData.destination.dateAndTime.split(" ")[1]}", + "Sosește la ${destinationStation.arrival!.scheduleTime}", textAlign: TextAlign.center, ), ], @@ -177,6 +174,9 @@ class LastUpdate extends StatelessWidget { @override Widget build(BuildContext context) { + if (trainData.status == null) { + return Container(); + } return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -188,62 +188,63 @@ class LastUpdate extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(2.0), - child: Text(trainData.lastInfo.station, textAlign: TextAlign.left,), + child: Text(trainData.status!.station, textAlign: TextAlign.left,), ), Expanded(child: Container(),), Padding( padding: const EdgeInsets.all(2.0), - child: Text(trainData.lastInfo.event, textAlign: TextAlign.right,), + child: Text(trainData.status!.state.toString(), textAlign: TextAlign.right,), ) ], ), Padding( padding: const EdgeInsets.all(2.0), - child: trainData.lastInfo.delay == 0 + child: trainData.status!.delay == 0 ? Text("Fără întârziere", style: Theme.of(context).textTheme.caption,) - : trainData.lastInfo.delay > 0 - ? Text("${trainData.lastInfo.delay} minute întârziere", style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.red.shade700),) - : Text("${-(trainData.lastInfo.delay)} minute mai devreme", style: Theme.of(context).textTheme.bodyText2.copyWith(color: Colors.green.shade700),) - ), - Padding( - padding: const EdgeInsets.all(2.0), - child: Text("Raportat la ${trainData.lastInfo.dateAndTime}"), + : trainData.status!.delay > 0 + ? Text("${trainData.status!.delay} minute întârziere", style: Theme.of(context).textTheme.bodyText2?.copyWith(color: Colors.red.shade700),) + : Text("${-(trainData.status!.delay)} minute mai devreme", style: Theme.of(context).textTheme.bodyText2?.copyWith(color: Colors.green.shade700),) ), + // TODO: Implement status report time detection + // Padding( + // padding: const EdgeInsets.all(2.0), + // child: Text("Raportat la ${trainData.lastInfo.dateAndTime}"), + // ), ], ); } } -class NextStop extends StatelessWidget { - final TrainData trainData; - NextStop(this.trainData); +// class NextStop extends StatelessWidget { +// final TrainData trainData; +// NextStop(this.trainData); - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(2.0), - child: Text("Următoarea oprire", style: Theme.of(context).textTheme.headline5,), - ), - Row( - children: [ - Padding( - padding: const EdgeInsets.all(2.0), - child: Text(trainData.nextStop.station, textAlign: TextAlign.left,), - ), - Expanded(child: Container(),), - Padding( - padding: const EdgeInsets.all(2.0), - child: Text(trainData.nextStop.dateAndTime, textAlign: TextAlign.right,), - ) - ], - ), - ], - ); - } -} +// @override +// Widget build(BuildContext context) { +// return Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Padding( +// padding: const EdgeInsets.all(2.0), +// child: Text("Următoarea oprire", style: Theme.of(context).textTheme.headline5,), +// ), +// Row( +// children: [ +// Padding( +// padding: const EdgeInsets.all(2.0), +// child: Text(trainData.nextStop.station, textAlign: TextAlign.left,), +// ), +// Expanded(child: Container(),), +// Padding( +// padding: const EdgeInsets.all(2.0), +// child: Text(trainData.nextStop.dateAndTime, textAlign: TextAlign.right,), +// ) +// ], +// ), +// ], +// ); +// } +// } class TotalDetails extends StatelessWidget { final TrainData trainData; @@ -254,18 +255,18 @@ class TotalDetails extends StatelessWidget { return Row( children: [ Text( - trainData.distance, + '${trainData.stations.last.km} km', style: Theme.of(context).textTheme.caption, textAlign: TextAlign.left, ), Expanded( child: Container() ), - Text( - trainData.tripLength, - style: Theme.of(context).textTheme.caption, - textAlign: TextAlign.right, - ) + // Text( + // trainData.tripLength, + // style: Theme.of(context).textTheme.caption, + // textAlign: TextAlign.right, + // ) ], ); } diff --git a/lib/train_info_page/train_info.dart b/lib/train_info_page/train_info.dart deleted file mode 100644 index 7b25a21..0000000 --- a/lib/train_info_page/train_info.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'dart:io' show Platform; - -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; - -import 'package:info_tren/models/train_data.dart'; -import 'package:info_tren/train_info_page/train_info_cupertino.dart'; -import 'package:info_tren/train_info_page/train_info_material.dart'; - -mixin TrainInfoMixin { - String title; - bool showTrainData; - TrainLookupResult lookupResult; - bool requestedData; -} - -class TrainInfo extends StatelessWidget { - static String routeName = "/trainInfo/display"; - - final int trainNumber; - - TrainInfo({@required this.trainNumber}); - - @override - Widget build(BuildContext context) { - return TrainDataWebViewAdapter( - builder: (context) { - if (Platform.isAndroid) { - return TrainInfoMaterial(trainNumber: trainNumber,); - } - else if (Platform.isIOS) { - return TrainInfoCupertino(trainNumber: trainNumber,); - } - - return null; - }, - ); - } -} - -typedef FutureDisplayCallback(BuildContext context, T data); - -class FutureDisplay extends StatelessWidget { - final Future future; - final FutureDisplayCallback builder; - - FutureDisplay({Key key, @required this.future, @required this.builder}): super(key: key); - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: future, - builder: (context, snapshot) { - if (snapshot.hasData) return builder(context, snapshot.data); - if (snapshot.hasError) throw snapshot.error; - if (snapshot.connectionState == ConnectionState.done) return Container(); - - Widget loadingWidget; - if (Platform.isAndroid) { - loadingWidget = CircularProgressIndicator(); - } - else if (Platform.isIOS) { - loadingWidget = CupertinoActivityIndicator(); - } - - return Center( - child: loadingWidget, - ); - }, - ); - } -} \ No newline at end of file diff --git a/lib/train_info_page/train_info_cupertino.dart b/lib/train_info_page/train_info_cupertino.dart deleted file mode 100644 index 93aa3ec..0000000 --- a/lib/train_info_page/train_info_cupertino.dart +++ /dev/null @@ -1,1022 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:info_tren/models/train_data.dart'; -import 'package:info_tren/train_info_page/train_info.dart'; -import 'package:info_tren/train_info_page/train_info_animation_helpers.dart'; -import 'package:info_tren/train_info_page/train_info_constants.dart'; -import 'package:info_tren/train_info_page/train_info_cupertino_DisplayTrainStation.dart'; -import 'package:info_tren/utils/stream_list.dart'; - -class TrainInfoCupertino extends StatefulWidget { - final int trainNumber; - - TrainInfoCupertino({@required this.trainNumber}); - - @override - _TrainInfoCupertinoState createState() => _TrainInfoCupertinoState(); -} - -class _TrainInfoCupertinoState extends State with TrainInfoMixin { - @override - void initState() { - super.initState(); - - title = widget.trainNumber.toString(); - showTrainData = false; - requestedData = false; - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - if (!requestedData) { - requestedData = true; - - TrainDataWebViewAdapter.of(context).loadTrain(widget.trainNumber).then((value) { - setState(() { - lookupResult = value; - }); - - if (lookupResult == TrainLookupResult.NOT_FOUND) { - Future.delayed(Duration(seconds: 5), () { - Navigator.of(context).pop(); - }); - } - else if (lookupResult == TrainLookupResult.FOUND) { - Future.delayed(Duration(seconds: 1, milliseconds: 500), () { - setState(() { - showTrainData = true; - }); - }); - } - }); - } - } - - @override - Widget build(BuildContext context) { - if (!showTrainData) { - return _TrainDataCupertinoBefore( - title: title, - lookupResult: lookupResult, - ); - } - else { - return _TrainDataCupertinoAfter( - title: title, - ); - } - } -} - -class _TrainDataCupertinoBefore extends StatefulWidget { - final String title; - final TrainLookupResult lookupResult; - - _TrainDataCupertinoBefore({ - @required this.title, - @required this.lookupResult, - }); - - @override - __TrainDataCupertinoBeforeState createState() => __TrainDataCupertinoBeforeState(); -} - -class __TrainDataCupertinoBeforeState extends State<_TrainDataCupertinoBefore> { - @override - Widget build(BuildContext context) { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text(widget.title ?? ""), - ), - child: SafeArea( - child: StreamBuilder( - stream: TrainDataWebViewAdapter.of(context).progressStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - return Container(); - case ConnectionState.waiting: - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CupertinoActivityIndicator(), - Text( - "Conectare...", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ); - case ConnectionState.active: - break; - case ConnectionState.done: - Navigator.of(context).pop(); - return Container(); - } - - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ProgressReportDisplayEntry( - key: ValueKey(1), - completed: 1 <= snapshot.data.current, - waitingText: "Se crează WebView", - completedText: "WebView a fost creat", - ), - ProgressReportDisplayEntry( - key: ValueKey(2), - completed: 2 <= snapshot.data.current, - waitingText: "Se încarcă pagina Informatica Feroviară", - completedText: "Pagina Informatica Feroviară a fost încărcată", - ), - ProgressReportDisplayEntry( - key: ValueKey(3), - completed: 3 <= snapshot.data.current, - waitingText: "Se încarcă informațiile despre tren", - completedText: "Informațiile despre tren au fost încărcate", - ), - if (widget.lookupResult != null) - ...[ - Container(height: 20,), - SizedBox( - width: double.infinity, - child: AnimatedBackground( - animationDuration: Duration(milliseconds: 250), - initialColor: CupertinoTheme.of(context).scaffoldBackgroundColor, - backgroundColor: - widget.lookupResult == TrainLookupResult.FOUND - ? BACKGROUND_GREEN - : BACKGROUND_RED, - child: Center( - child: Row( - children: [ - Expanded(child: Container(),), - if (widget.lookupResult == TrainLookupResult.FOUND) - Padding( - padding: const EdgeInsets.fromLTRB(8, 8, 0, 8), - child: Center( - child: CupertinoActivityIndicator() - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - widget.lookupResult == TrainLookupResult.FOUND - ? "Trenul a fost găsit" - : widget.lookupResult == TrainLookupResult.NOT_FOUND - ? "Trenul nu a fost găsit" - : "A apărut o eroare în căutarea trenului", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 20), - ), - ), - Expanded(child: Container(),), - ], - ), - ), - ), - ), - ], - ], - ), - ); - } - ), - ), - ); - } -} - -class _TrainDataCupertinoAfter extends StatefulWidget { - final String title; - - _TrainDataCupertinoAfter({ - @required this.title, - }); - - @override - __TrainDataCupertinoAfterState createState() => __TrainDataCupertinoAfterState(); -} - -class __TrainDataCupertinoAfterState extends State<_TrainDataCupertinoAfter> { - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () { - Navigator.of(context).pop(); - }), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text(widget.title ?? ""), - ), - child: SafeArea( - child: Center( - child: CupertinoActivityIndicator(), - ), - ), - ); - } - - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: FutureBuilder>( - future: Future.wait([ - snapshot.data.rang, - snapshot.data.trainNumber - ]), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text("Informații despre ${snapshot.data[0]} ${snapshot.data[1]}"); - } - else { - return Text(widget.title ?? ""); - } - }, - ), - ), - child: SafeArea( - top: false, - bottom: false, - child: Builder( - builder: (context) { - final topPadding = MediaQuery.of(context).padding.top; - - return CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.only( - top: topPadding, - ), - child: Container(), - ), - ), - DisplayTrainID(trainData: snapshot.data,), - DisplayTrainOperator(trainData: snapshot.data,), - DisplayTrainRoute(trainData: snapshot.data,), - DisplayTrainDeparture(trainData: snapshot.data,), - SliverToBoxAdapter( - child: CupertinoDivider( - color: FOREGROUND_WHITE, - ), - ), - DisplayTrainLastInfo(trainData: snapshot.data,), - SliverToBoxAdapter( - child: CupertinoDivider(), - ), - SliverToBoxAdapter( - child: IntrinsicHeight( - child: Row( - children: [ - Expanded( - child: DisplayTrainNextStop(trainData: snapshot.data,), - ), - SizedBox( - height: double.infinity, - child: CupertinoVerticalDivider(), - ), - Expanded( - child: DisplayTrainDestination(trainData: snapshot.data,), - ) - ], - ), - ), - ), - SliverToBoxAdapter( - child: CupertinoDivider(), - ), - SliverToBoxAdapter( - child: IntrinsicHeight( - child: Row( - children: [ - Expanded( - child: DisplayTrainRouteDuration(trainData: snapshot.data,), - ), - SizedBox( - height: double.infinity, - child: CupertinoVerticalDivider(), - ), - Expanded( - child: DisplayTrainRouteDistance(trainData: snapshot.data,), - ) - ], - ), - ), - ), - SliverToBoxAdapter( - child: CupertinoDivider( - color: FOREGROUND_WHITE, - ), - ), - DisplayTrainStations( - trainData: snapshot.data, - pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture, - ), - SliverToBoxAdapter( - child: Container( - height: MediaQuery.of(context).viewPadding.bottom, - ), - ), - ], - ); - } - ), - ), - ); - }, - ); - - // return CupertinoPageScaffold( - // navigationBar: CupertinoNavigationBar( - // middle: Text(title ?? ""), - // ), - // child: SafeArea( - // bottom: false, - // child: FutureBuilder( - // future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () { - // Navigator.of(context).pop(); - // }), - // builder: (context, snapshot) { - // if (!snapshot.hasData) { - // return Center( - // child: CupertinoActivityIndicator(), - // ); - // } - - // try { - // Future.wait([ - // snapshot.data.rang, - // snapshot.data.trainNumber - // ]).then((values) { - // setState(() { - // title = "Informații despre ${values[0]} ${values[1]}"; - // }); - // }); - - // return CustomScrollView( - // slivers: [ - // DisplayTrainID(data: snapshot.data,), - // DisplayTrainOperator(data: snapshot.data,), - // DisplayTrainRoute(data: snapshot.data,), - // DisplayTrainDeparture(data: snapshot.data,), - // SliverToBoxAdapter( - // child: CupertinoDivider( - // color: FOREGROUND_WHITE, - // ), - // ), - // DisplayTrainLastInfo(data: snapshot.data,), - // SliverToBoxAdapter( - // child: CupertinoDivider(), - // ), - // SliverToBoxAdapter( - // child: IntrinsicHeight( - // child: Row( - // children: [ - // Expanded( - // child: DisplayTrainNextStop(data: snapshot.data,), - // ), - // SizedBox( - // height: double.infinity, - // child: CupertinoVerticalDivider(), - // ), - // Expanded( - // child: DisplayTrainDestination(data: snapshot.data,), - // ) - // ], - // ), - // ), - // ), - // SliverToBoxAdapter( - // child: CupertinoDivider(), - // ), - // SliverToBoxAdapter( - // child: IntrinsicHeight( - // child: Row( - // children: [ - // Expanded( - // child: DisplayTrainRouteDuration(data: snapshot.data,), - // ), - // SizedBox( - // height: double.infinity, - // child: CupertinoVerticalDivider(), - // ), - // Expanded( - // child: DisplayTrainRouteDistance(data: snapshot.data,), - // ) - // ], - // ), - // ), - // ), - // SliverToBoxAdapter( - // child: CupertinoDivider( - // color: FOREGROUND_WHITE, - // ), - // ), - // DisplayTrainStations( - // data: snapshot.data, - // pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture, - // ), - // ], - // ); - // } - // on OnDemandInvalidatedException { - // Navigator.of(context).pop(); - // print("Got OnDemandInvalidatedException!"); - // return Container(); - // } - // }, - // ), - // ), - // ); - } -} - -class DisplayTrainID extends StatelessWidget { - final OnDemandTrainData trainData; - DisplayTrainID({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return SliverToBoxAdapter( - child: FutureDisplay( - future: Future.wait([ - trainData.rang, - trainData.trainNumber - ]), - builder: (context, datas) { - return Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "${datas[0]} ${datas[1]}", - style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle, - ), - ), - ); - }, - ), - ); - } -} - -class DisplayTrainRoute extends StatelessWidget { - final OnDemandTrainData trainData; - - DisplayTrainRoute({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return SliverToBoxAdapter( - child: FutureDisplay( - future: Future.wait([trainData.route.from, trainData.route.to]), - builder: (context, routePieces) { - return Row( - children: [ - Center( - child: Padding( - padding: const EdgeInsets.all(4), - child: Text( - routePieces[0], - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 16, - ), - ), - ), - ), - Expanded(child: Container(),), - Center(child: Text("-")), - Expanded(child: Container(),), - Center( - child: Padding( - padding: const EdgeInsets.all(4), - child: Text( - routePieces[1], - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 16, - ), - textAlign: TextAlign.right, - ), - ), - ), - ], - ); - }, - ), - ); - } -} - -class DisplayTrainOperator extends StatelessWidget { - final OnDemandTrainData trainData; - - DisplayTrainOperator({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return SliverToBoxAdapter( - child: FutureDisplay( - future: trainData.operator, - builder: (context, operator) { - return Center( - child: Text( - operator, - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 14, - fontStyle: FontStyle.italic, - ), - ), - ); - }, - ), - ); - } -} - -class DisplayTrainDeparture extends StatelessWidget { - final OnDemandTrainData trainData; - - DisplayTrainDeparture({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(2), - child: FutureDisplay( - future: trainData.departureDate, - builder: (context, dataPlecare) { - return Text( - "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontStyle: FontStyle.italic, - fontWeight: FontWeight.w200, - ), - textAlign: TextAlign.center, - ); - }, - ), - ), - ); - } -} - -class DisplayTrainLastInfo extends StatelessWidget { - final OnDemandTrainData trainData; - - DisplayTrainLastInfo({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return SliverToBoxAdapter( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Center( - child: Padding( - padding: const EdgeInsets.all(2), - child: Text( - "Ultima informație", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - Row( - children: [ - Padding( - padding: const EdgeInsets.all(4), - child: FutureDisplay( - future: trainData.lastInfo.station, - builder: (context, station) { - return Text( - station, - style: CupertinoTheme.of(context).textTheme.textStyle, - textAlign: TextAlign.left, - ); - }, - ), - ), - Expanded(child: Container(),), - Padding( - padding: const EdgeInsets.all(4), - child: FutureDisplay( - future: trainData.lastInfo.event, - builder: (context, event) { - return Text( - event, - style: CupertinoTheme.of(context).textTheme.textStyle, - textAlign: TextAlign.right, - ); - }, - ), - ), - ], - ), - FutureDisplay( - future: trainData.lastInfo.dateAndTime, - builder: (context, dt) { - return Text( - "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}", - textAlign: TextAlign.center, - ); - }, - ), - FutureBuilder( - initialData: 0, - future: trainData.lastInfo.delay, - builder: (context, snapshot) { - if (snapshot.data == 0) { - return Container(); - } - - if (snapshot.data > 0) { - return Text( - "${snapshot.data} minute întârziere", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 14, - color: Color.fromRGBO(200, 30, 15, 1), - ), - ); - } - else { - return Text( - "${-snapshot.data} minute mai devreme", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 12, - color: Color.fromRGBO(15, 200, 15, 1), - ), - ); - } - }, - ) - ], - ), - ); - } -} - -class DisplayTrainNextStop extends StatelessWidget { - final OnDemandTrainData trainData; - - DisplayTrainNextStop({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: trainData.nextStop.stationName, - builder: (context, snapshot) { - if (!snapshot.hasData) return Container(); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(4), - child: Text( - "Următoarea oprire", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ), - CupertinoDivider( - color: Color.fromRGBO(15, 15, 15, 1), - ), - FutureDisplay( - future: trainData.nextStop.stationName, - builder: (context, station) { - return Padding( - padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), - child: Text( - station, - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 18, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, - ), - ); - }, - ), - FutureDisplay( - future: trainData.nextStop.arrival, - builder: (context, arrival) { - const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 14, - ), - textAlign: TextAlign.center, - ), - Text( - "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 14, - ), - textAlign: TextAlign.center, - ), - ], - ); - }, - ) - ], - ); - } - ); - } -} - -class DisplayTrainDestination extends StatelessWidget { - final OnDemandTrainData trainData; - - DisplayTrainDestination({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: trainData.destination.stationName, - builder: (context, snapshot) { - if (!snapshot.hasData) return Container(); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(4), - child: Text( - "Destinația", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ), - CupertinoDivider( - color: Color.fromRGBO(15, 15, 15, 1), - ), - FutureDisplay( - future: trainData.destination.stationName, - builder: (context, station) { - return Padding( - padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), - child: Text( - station, - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 18, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, - ), - ); - }, - ), - FutureDisplay( - 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: [ - 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 DisplayTrainRouteDistance extends StatelessWidget { - final OnDemandTrainData trainData; - - DisplayTrainRouteDistance({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Distanța rutei", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - FutureDisplay( - future: trainData.routeDistance, - builder: (context, distance) { - return Text( - "$distance km", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 16, - ), - textAlign: TextAlign.center, - ); - }, - ), - ], - ); - } -} - -class DisplayTrainRouteDuration extends StatelessWidget { - final OnDemandTrainData trainData; - - DisplayTrainRouteDuration({@required this.trainData}); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Durata rutei", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - FutureDisplay( - future: trainData.routeDuration, - builder: (context, duration) { - var durationString = StringBuffer(); - - bool firstWritten = false; - - if (duration.inDays > 0) { - firstWritten = true; - if (duration.inDays == 1) durationString.write("1 zi"); - else durationString.write("${duration.inDays} zile"); - duration -= Duration(days: duration.inDays); - } - - if (duration.inHours > 0) { - if (firstWritten) { - durationString.write(", "); - } - firstWritten = true; - if (duration.inHours == 1) durationString.write("1 oră"); - else durationString.write("${duration.inHours} ore"); - duration -= Duration(hours: duration.inHours); - } - - if (duration.inMinutes > 0) { - if (firstWritten) { - durationString.write(", "); - } - firstWritten = true; - if (duration.inMinutes == 1) durationString.write("1 minut"); - else durationString.write("${duration.inMinutes} minute"); - duration -= Duration(minutes: duration.inMinutes); - } - - return Text( - durationString.toString(), - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 16, - ), - textAlign: TextAlign.center, - ); - }, - ), - ], - ); - } -} - -class DisplayTrainStations extends StatelessWidget { - final OnDemandTrainData trainData; - final Future pageLoadFuture; - - DisplayTrainStations({@required this.trainData, @required this.pageLoadFuture}); - - @override - Widget build(BuildContext context) { - return StreamBuilder>( - stream: listifyStream(trainData.stations(pageLoadFuture: pageLoadFuture)), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return SliverToBoxAdapter( - child: Container(), - ); - } - - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index.isOdd) { - return CupertinoDivider(); - } - else { - final itemIndex = index ~/ 2; - return IndexedSemantics( - child: DisplayTrainStation( - station: snapshot.data[itemIndex], - ), - index: itemIndex, - ); - } - }, - childCount: snapshot.data.length * 2 - 1, - addSemanticIndexes: false, - ), - ); - }, - ); - } -} - -class CupertinoDivider extends StatelessWidget { - final Color color; - - CupertinoDivider({Key key, Color color}): - color = color ?? FOREGROUND_DARK_GREY, - super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: 1, - ), - Container( - height: 1, - decoration: BoxDecoration( - color: color, - ), - ), - Container( - height: 1, - ), - ], - ); - } -} - -class CupertinoVerticalDivider extends StatelessWidget { - final Color color; - - CupertinoVerticalDivider({Key key, Color color}): - color = color ?? FOREGROUND_DARK_GREY, - super(key: key); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 1, - ), - Container( - width: 1, - decoration: BoxDecoration( - color: color, - ), - ), - Container( - width: 1, - ), - ], - ); - } -} \ No newline at end of file diff --git a/lib/train_info_page/train_info_cupertino_DisplayTrainStation.dart b/lib/train_info_page/train_info_cupertino_DisplayTrainStation.dart deleted file mode 100644 index bf6b360..0000000 --- a/lib/train_info_page/train_info_cupertino_DisplayTrainStation.dart +++ /dev/null @@ -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: [ - Row( - mainAxisSize: MainAxisSize.max, - children: [ - 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: [ - Expanded( - child: Center( - child: FutureDisplay( - 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>( - 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>( - 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: [ - 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>( - future: Future.wait([ - station.arrivalTime, - station.delay, - ]), - builder: (context, data) { - if (finalStation) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - "→", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 22, - ), - ), - Container(width: 2,), - Text("sosire la "), - ArrivalTime(station: station,), - Expanded(child: Container(),), - ], - ); - } - else { - 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: [ - 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: [ - 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( - future: station.stopsFor, - builder: (context, stopsFor) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - 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>( - future: Future.wait([ - station.departureTime, - station.delay, - ]), - builder: (context, data) { - if (firstStation) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded(child: Container(),), - Text("plecare la "), - DepartureTime(station: station,), - Container(width: 2,), - Text( - "→", - style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 22, - ), - ), - ], - ); - } - else { - 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: [ - 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: [ - 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( - 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(); - }, - ); - } -} - diff --git a/lib/train_info_page/train_info_material.dart b/lib/train_info_page/train_info_material.dart deleted file mode 100644 index 3f8e39d..0000000 --- a/lib/train_info_page/train_info_material.dart +++ /dev/null @@ -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 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( - 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: [ - 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: [ - 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: [ - 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( - 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>( - 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: [ - if (isSmallScreen(context)) - FutureBuilder>( - 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: [ - 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: [ - Expanded(child: DisplayTrainNextStop(trainData: snapshot.data,)), - Expanded(child: DisplayTrainDestination(trainData: snapshot.data,)), - ], - ), - ), - ), - SliverToBoxAdapter( - child: IntrinsicHeight( - child: Row( - children: [ - 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>( - 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( - 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: [ - 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( - 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: [ - 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: [ - 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: [ - FutureDisplay( - future: trainData.lastInfo.dateAndTime, - builder: (context, dt) { - return Text( - "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}", - textAlign: TextAlign.center, - ); - }, - ), - Expanded(child: Container(),), - 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: [ - Padding( - padding: const EdgeInsets.all(4), - child: Text( - "Următoarea oprire", - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: isSmallScreen(context) ? 18 : 20, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ), - FutureDisplay( - future: trainData.nextStop.stationName, - builder: (context, station) { - return Padding( - padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), - child: Text( - station, - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: isSmallScreen(context) ? 16 : 18, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, - ), - ); - }, - ), - FutureDisplay( - future: trainData.nextStop.arrival, - builder: (context, arrival) { - const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: isSmallScreen(context) ? 12 : 14, - ), - textAlign: TextAlign.center, - ), - Text( - "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: isSmallScreen(context) ? 12 : 14, - ), - textAlign: TextAlign.center, - ), - ], - ); - }, - ) - ], - ), - ), - ), - ); - } - ); - } -} - -class DisplayTrainDestination extends StatelessWidget { - final 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: [ - 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( - 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: [ - 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: [ - 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: [ - Text( - "Durata rutei", - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: isSmallScreen(context) ? 16 : 18, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - FutureDisplay( - future: trainData.routeDuration, - builder: (context, duration) { - var durationString = StringBuffer(); - - bool firstWritten = false; - - if (duration.inDays > 0) { - firstWritten = true; - if (duration.inDays == 1) durationString.write("1 zi"); - else durationString.write("${duration.inDays} zile"); - duration -= Duration(days: duration.inDays); - } - - if (duration.inHours > 0) { - if (firstWritten) { - durationString.write(", "); - } - firstWritten = true; - if (duration.inHours == 1) durationString.write("1 oră"); - else durationString.write("${duration.inHours} ore"); - duration -= Duration(hours: duration.inHours); - } - - if (duration.inMinutes > 0) { - if (firstWritten) { - durationString.write(", "); - } - firstWritten = true; - if (duration.inMinutes == 1) durationString.write("1 minut"); - else durationString.write("${duration.inMinutes} minute"); - duration -= Duration(minutes: duration.inMinutes); - } - - return Text( - durationString.toString(), - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: isSmallScreen(context) ? 14 : 16, - ), - textAlign: TextAlign.center, - ); - }, - ), - ], - ), - ), - ), - ); - } -} - -class DisplayTrainStations extends StatelessWidget { - final OnDemandTrainData trainData; - final Future pageLoadFuture; - - DisplayTrainStations({@required this.trainData, @required this.pageLoadFuture}); - - @override - Widget build(BuildContext context) { - return StreamBuilder>( - 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: [ - Container( - height: size, - width: size, - child: (ModalRoute.of(context)?.canPop ?? false) - ? BackButtonIcon() - : null, - ), - Expanded( - child: Center( - child: Padding( - padding: const EdgeInsets.all(2), - child: Text( - title, - textAlign: TextAlign.center, - style: - Theme.of(context).appBarTheme.textTheme?.caption?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.bodyText2?.color) ?? - Theme.of(context).textTheme.caption.copyWith(color: Theme.of(context).textTheme.bodyText2.color), - ), - ), - ), - ), - Container( - height: size, - width: size, - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/train_info_page/train_info_material_DisplayTrainStation.dart b/lib/train_info_page/train_info_material_DisplayTrainStation.dart deleted file mode 100644 index 31bff16..0000000 --- a/lib/train_info_page/train_info_material_DisplayTrainStation.dart +++ /dev/null @@ -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: [ - Row( - mainAxisSize: MainAxisSize.max, - children: [ - 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: [ - Expanded( - child: Center( - child: FutureDisplay( - 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>( - 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>( - 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: [ - 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>( - future: Future.wait([ - station.arrivalTime, - station.delay, - ]), - builder: (context, data) { - if (finalStation) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - "→", - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: isSmallScreen(context) ? 18 : 22, - ), - ), - Container(width: 2,), - Text("sosire la "), - ArrivalTime(station: station,), - Expanded(child: Container(),), - ], - ); - } - else { - 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: [ - 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: [ - 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( - future: station.stopsFor, - builder: (context, stopsFor) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - 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>( - future: Future.wait([ - station.departureTime, - station.delay, - ]), - builder: (context, data) { - if (firstStation) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded(child: Container(),), - Text("plecare la "), - DepartureTime(station: station,), - Container(width: 2,), - Text( - "→", - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: 22, - ), - ), - ], - ); - } - else { - 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: [ - 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: [ - 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( - 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(); - }, - ); - } -} diff --git a/lib/train_info_page/train_info_prompt.dart b/lib/train_info_page/train_info_prompt.dart deleted file mode 100644 index 32dcca4..0000000 --- a/lib/train_info_page/train_info_prompt.dart +++ /dev/null @@ -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 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: [ - ...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: [ - 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: [ - 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: [ - getLineListItem( - context, - op: TrainOperatorLines(), - line: _TrainOperatorTrainDescription() - ), - Divider(), - ], - ), - delegate: SliverChildBuilderDelegate( - (context, index) { - return Column( - children: [ - 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: [ - 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 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: [ - 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 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: [ - 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 json) => _$TrainOperatorLinesFromJson(json); - Map toJson() => _$TrainOperatorLinesToJson(this); -} - -@JsonSerializable() -class _TrainOperatorTrainDescription { - final String rang; - @JsonKey(name: "numar") - final String number; - @JsonKey(name: "numar_intern") - final int internalNumber; - - _TrainOperatorTrainDescription({ - this.number, - this.rang, - this.internalNumber - }); - - factory _TrainOperatorTrainDescription.fromJson(Map json) => _$_TrainOperatorTrainDescriptionFromJson(json); - Map toJson() => _$_TrainOperatorTrainDescriptionToJson(this); -} \ No newline at end of file diff --git a/lib/train_info_page/train_info_prompt.g.dart b/lib/train_info_page/train_info_prompt.g.dart deleted file mode 100644 index fed6cd2..0000000 --- a/lib/train_info_page/train_info_prompt.g.dart +++ /dev/null @@ -1,44 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'train_info_prompt.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -TrainOperatorLines _$TrainOperatorLinesFromJson(Map 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)) - ?.toList()); -} - -Map _$TrainOperatorLinesToJson(TrainOperatorLines instance) => - { - 'short_name': instance.shortName, - 'operator': instance.operator, - 'versiune': instance.version, - 'trenuri': instance.trains - }; - -_TrainOperatorTrainDescription _$_TrainOperatorTrainDescriptionFromJson( - Map json) { - return _TrainOperatorTrainDescription( - number: json['numar'] as String, - rang: json['rang'] as String, - internalNumber: json['numar_intern'] as int); -} - -Map _$_TrainOperatorTrainDescriptionToJson( - _TrainOperatorTrainDescription instance) => - { - 'rang': instance.rang, - 'numar': instance.number, - 'numar_intern': instance.internalNumber - }; diff --git a/lib/utils/default_ui_design.dart b/lib/utils/default_ui_design.dart new file mode 100644 index 0000000..5c9cd1e --- /dev/null +++ b/lib/utils/default_ui_design.dart @@ -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; + } +} \ No newline at end of file diff --git a/lib/utils/state_to_string.dart b/lib/utils/state_to_string.dart new file mode 100644 index 0000000..7058d0f --- /dev/null +++ b/lib/utils/state_to_string.dart @@ -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'; + } +} \ No newline at end of file diff --git a/lib/utils/string.dart b/lib/utils/string.dart new file mode 100644 index 0000000..73f312d --- /dev/null +++ b/lib/utils/string.dart @@ -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(); + } +} diff --git a/lib/utils/webview_invoke.dart b/lib/utils/webview_invoke.dart deleted file mode 100644 index 8dba886..0000000 --- a/lib/utils/webview_invoke.dart +++ /dev/null @@ -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 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; -} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index bd5cd45..f1474ef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: + flutter_redux: + dependency: "direct main" + description: + name: flutter_redux + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.2" + frontend_server_client: dependency: transitive description: - name: front_end + name: frontend_server_client url: "https://pub.dartlang.org" source: hosted - version: "0.1.19" + 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" diff --git a/pubspec.yaml b/pubspec.yaml index d601ff1..497e59a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..3022fe4 --- /dev/null +++ b/web/index.html @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + info_tren + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..89f1a81 --- /dev/null +++ b/web/manifest.json @@ -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" + } + ] +}