Compare commits

..

13 Commits

  1. 14
      .metadata
  2. 12
      CHANGELOG.txt
  3. 4
      android/app/build.gradle
  4. 4
      android/build.gradle
  5. 2
      android/gradle/wrapper/gradle-wrapper.properties
  6. 216
      codemagic.yaml
  7. 2
      ios/Flutter/AppFrameworkInfo.plist
  8. 2
      ios/Podfile
  9. 34
      ios/Podfile.lock
  10. 76
      ios/Runner.xcodeproj/project.pbxproj
  11. 2
      ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  12. 3
      ios/Runner.xcworkspace/contents.xcworkspacedata
  13. 2
      ios/Runner/Info.plist
  14. 2
      lib/api/releases.dart
  15. 8
      lib/api/station_data.dart
  16. 2
      lib/api/train_data.dart
  17. 71
      lib/components/cupertino_listtile.dart
  18. 2
      lib/components/select_train_suggestions/select_train_suggestions.dart
  19. 5
      lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart
  20. 5
      lib/components/select_train_suggestions/select_train_suggestions_fluent.dart
  21. 3
      lib/components/select_train_suggestions/select_train_suggestions_material.dart
  22. 2
      lib/components/slim_app_bar.dart
  23. 39
      lib/components/train_id_text_span.dart
  24. 144
      lib/main.dart
  25. 7
      lib/models.dart
  26. 10
      lib/models/changelog_entry.dart
  27. 2
      lib/models/station_arrdep.freezed.dart
  28. 4
      lib/models/station_data.freezed.dart
  29. 2
      lib/models/station_status.freezed.dart
  30. 3
      lib/models/station_train.freezed.dart
  31. 3
      lib/models/stations_result.freezed.dart
  32. 373
      lib/models/train_data.dart
  33. 2365
      lib/models/train_data.freezed.dart
  34. 217
      lib/models/train_data.g.dart
  35. 2
      lib/models/trains_result.freezed.dart
  36. 8
      lib/models/ui_design.dart
  37. 94
      lib/models/ui_timezone.dart
  38. 5
      lib/pages/about/about_page.dart
  39. 4
      lib/pages/about/about_page_cupertino.dart
  40. 198
      lib/pages/about/about_page_fluent.dart
  41. 64
      lib/pages/about/about_page_material.dart
  42. 13
      lib/pages/main/main_page.dart
  43. 16
      lib/pages/main/main_page_fluent.dart
  44. 38
      lib/pages/settings/setings_page.dart
  45. 96
      lib/pages/settings/settings_page_cupertino.dart
  46. 65
      lib/pages/settings/settings_page_fluent.dart
  47. 66
      lib/pages/settings/settings_page_material.dart
  48. 3
      lib/pages/station_arrdep_page/view_station/view_station.dart
  49. 43
      lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart
  50. 49
      lib/pages/station_arrdep_page/view_station/view_station_fluent.dart
  51. 51
      lib/pages/station_arrdep_page/view_station/view_station_material.dart
  52. 111
      lib/pages/train_info_page/view_train/train_info.dart
  53. 113
      lib/pages/train_info_page/view_train/train_info_cupertino.dart
  54. 332
      lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart
  55. 20
      lib/pages/train_info_page/view_train/train_info_fluent.dart
  56. 368
      lib/pages/train_info_page/view_train/train_info_fluent_DisplayTrainStation.dart
  57. 143
      lib/pages/train_info_page/view_train/train_info_material.dart
  58. 406
      lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart
  59. 81
      lib/providers.dart
  60. 120
      lib/providers.g.dart
  61. 15
      lib/utils/state_to_string.dart
  62. 4
      linux/flutter/generated_plugin_registrant.cc
  63. 1
      linux/flutter/generated_plugins.cmake
  64. 8
      linux/infotren.desktop
  65. 39
      linux/install.sh
  66. 1
      macos/Flutter/Flutter-Debug.xcconfig
  67. 1
      macos/Flutter/Flutter-Release.xcconfig
  68. 40
      macos/Podfile
  69. 41
      macos/Podfile.lock
  70. 71
      macos/Runner.xcodeproj/project.pbxproj
  71. 2
      macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  72. 3
      macos/Runner.xcworkspace/contents.xcworkspacedata
  73. 649
      pubspec.lock
  74. 15
      pubspec.yaml
  75. 17
      windows/.gitignore
  76. 101
      windows/CMakeLists.txt
  77. 104
      windows/flutter/CMakeLists.txt
  78. 17
      windows/flutter/generated_plugin_registrant.cc
  79. 15
      windows/flutter/generated_plugin_registrant.h
  80. 25
      windows/flutter/generated_plugins.cmake
  81. 39
      windows/runner/CMakeLists.txt
  82. 121
      windows/runner/Runner.rc
  83. 61
      windows/runner/flutter_window.cpp
  84. 33
      windows/runner/flutter_window.h
  85. 43
      windows/runner/main.cpp
  86. 16
      windows/runner/resource.h
  87. BIN
      windows/runner/resources/app_icon.ico
  88. 20
      windows/runner/runner.exe.manifest
  89. 64
      windows/runner/utils.cpp
  90. 19
      windows/runner/utils.h
  91. 245
      windows/runner/win32_window.cpp
  92. 98
      windows/runner/win32_window.h

14
.metadata

@ -4,8 +4,8 @@
# This file should be version controlled. # This file should be version controlled.
version: version:
revision: bcea432bce54a83306b3c00a7ad0ed98f777348d revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
channel: beta channel: stable
project_type: app project_type: app
@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: bcea432bce54a83306b3c00a7ad0ed98f777348d create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: bcea432bce54a83306b3c00a7ad0ed98f777348d base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: linux - platform: windows
create_revision: bcea432bce54a83306b3c00a7ad0ed98f777348d create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: bcea432bce54a83306b3c00a7ad0ed98f777348d base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
# User provided section # User provided section

12
CHANGELOG.txt

@ -1,3 +1,15 @@
v2.7.11
Add support for IC trains.
Allow choosing displayed timezone.
Show notes about wagon detachment, receival, or train number changes.
Use system accent color if available.
Use API v3.
v2.7.10
Add about page to Fluent UI.
Add settings page, allowing changing between UIs.
Add touch scrolling on Linux.
v2.7.9 v2.7.9
Add Fluent UI for Windows and Linux. Add Fluent UI for Windows and Linux.
Add split view in landscape when viewing a train. Add split view in landscape when viewing a train.

4
android/app/build.gradle

@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 31 compileSdkVersion 33
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -51,7 +51,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "ro.dcdev.infotren" applicationId "ro.dcdev.infotren"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 31 targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }

4
android/build.gradle

@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
@ -24,6 +24,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

2
android/gradle/wrapper/gradle-wrapper.properties vendored

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

216
codemagic.yaml

File diff suppressed because one or more lines are too long

2
ios/Flutter/AppFrameworkInfo.plist

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>9.0</string> <string>11.0</string>
</dict> </dict>
</plist> </plist>

2
ios/Podfile

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '9.0' # platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

34
ios/Podfile.lock

@ -0,0 +1,34 @@
PODS:
- Flutter (1.0.0)
- package_info_plus (0.4.5):
- Flutter
- shared_preferences_ios (0.0.1):
- Flutter
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.11.3

76
ios/Runner.xcodeproj/project.pbxproj

@ -13,6 +13,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
AF5528149967EA996B5AA109 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E6EB5FA5AA2228D5622CD62 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@ -31,7 +32,11 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2088AE25E07C211FFB9CE536 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
2F80AD107B0E1CC9E1C01A5A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
5DA42B3CD8940DB121C028E8 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
6E6EB5FA5AA2228D5622CD62 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@ -49,6 +54,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
AF5528149967EA996B5AA109 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -72,6 +78,8 @@
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
B55F9B76DFEAB456725329A0 /* Pods */,
E56598AA51C5533E6B51BD5A /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -98,6 +106,25 @@
path = Runner; path = Runner;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B55F9B76DFEAB456725329A0 /* Pods */ = {
isa = PBXGroup;
children = (
2F80AD107B0E1CC9E1C01A5A /* Pods-Runner.debug.xcconfig */,
2088AE25E07C211FFB9CE536 /* Pods-Runner.release.xcconfig */,
5DA42B3CD8940DB121C028E8 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
E56598AA51C5533E6B51BD5A /* Frameworks */ = {
isa = PBXGroup;
children = (
6E6EB5FA5AA2228D5622CD62 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -105,12 +132,14 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
2B2F3198BD0D2214C77EC99E /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
D71FC49A789443CEBF7C5C70 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -127,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1020; LastUpgradeCheck = 1300;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
@ -169,6 +198,28 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
2B2F3198BD0D2214C77EC99E /* [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;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -197,6 +248,23 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
}; };
D71FC49A789443CEBF7C5C70 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@ -272,7 +340,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -351,7 +419,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -400,7 +468,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;

2
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1300"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

3
ios/Runner.xcworkspace/contents.xcworkspacedata generated

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

2
ios/Runner/Info.plist

@ -43,5 +43,7 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict> </dict>
</plist> </plist>

2
lib/api/releases.dart

@ -12,5 +12,7 @@ Future<List<ChangelogEntry>> getRemoteReleases() async {
version: ChangelogVersion.parse(e['tag_name']), version: ChangelogVersion.parse(e['tag_name']),
description: e['body'], description: e['body'],
apkLink: (e['assets'] as List<dynamic>).where((e) => (e['name'] as String).contains('.apk')).map((e) => Uri.parse(e['browser_download_url'] as String)).firstOrNull, apkLink: (e['assets'] as List<dynamic>).where((e) => (e['name'] as String).contains('.apk')).map((e) => Uri.parse(e['browser_download_url'] as String)).firstOrNull,
linuxLink: (e['assets'] as List<dynamic>).where((e) => (e['name'] as String).contains('infotren-linux')).map((e) => Uri.parse(e['browser_download_url'] as String)).firstOrNull,
windowsLink: (e['assets'] as List<dynamic>).where((e) => (e['name'] as String).contains('-win.zip')).map((e) => Uri.parse(e['browser_download_url'] as String)).firstOrNull,
)).toList(); )).toList();
} }

8
lib/api/station_data.dart

@ -4,7 +4,11 @@ import 'package:http/http.dart' as http;
import 'package:info_tren/api/common.dart'; import 'package:info_tren/api/common.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
Future<StationData> getStationData(String stationName) async { Future<StationData> getStationData(String stationName, [DateTime? date]) async {
final response = await http.get(Uri.https(authority, 'v3/stations/$stationName')); final uri = Uri.https(authority, 'v3/stations/$stationName');
if (date != null) {
uri.queryParameters['date'] = date.toIso8601String();
}
final response = await http.get(uri);
return StationData.fromJson(jsonDecode(response.body)); return StationData.fromJson(jsonDecode(response.body));
} }

2
lib/api/train_data.dart

@ -4,7 +4,7 @@ import 'package:info_tren/models.dart';
Future<TrainData> getTrain(String trainNumber, {DateTime? date}) async { Future<TrainData> getTrain(String trainNumber, {DateTime? date}) async {
date ??= DateTime.now(); date ??= DateTime.now();
final response = await http.get(Uri.https(authority, 'v2/train/$trainNumber', { final response = await http.get(Uri.https(authority, 'v3/trains/$trainNumber', {
'date': date.toUtc().toIso8601String(), 'date': date.toUtc().toIso8601String(),
}),); }),);
return trainDataFromJson(response.body); return trainDataFromJson(response.body);

71
lib/components/cupertino_listtile.dart

@ -5,39 +5,54 @@ class CupertinoListTile extends StatelessWidget {
final Widget? title; final Widget? title;
final Widget? subtitle; final Widget? subtitle;
final Widget? trailing; final Widget? trailing;
final void Function()? onTap;
const CupertinoListTile({ Key? key, this.leading, this.title, this.subtitle, this.trailing, }) : super(key: key); const CupertinoListTile({ super.key, this.leading, this.title, this.subtitle, this.trailing, this.onTap, });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return GestureDetector(
mainAxisSize: MainAxisSize.max, onTap: onTap,
children: [ behavior: HitTestBehavior.opaque,
if (leading != null) child: Row(
leading!, mainAxisSize: MainAxisSize.max,
Expanded( children: [
child: Column( if (leading != null)
mainAxisSize: MainAxisSize.min, Padding(
children: [ padding: const EdgeInsets.all(8.0),
if (title != null) child: leading!,
title!, ),
if (subtitle != null) Expanded(
CupertinoTheme( child: Padding(
data: CupertinoTheme.of(context).copyWith( padding: const EdgeInsets.all(8.0),
textTheme: CupertinoTextThemeData( child: Column(
textStyle: TextStyle( mainAxisSize: MainAxisSize.min,
fontSize: CupertinoTheme.of(context).textTheme.textStyle.fontSize! - 2, crossAxisAlignment: CrossAxisAlignment.start,
) children: [
) if (title != null)
), title!,
child: subtitle!, if (subtitle != null)
), CupertinoTheme(
], data: CupertinoTheme.of(context).copyWith(
textTheme: CupertinoTextThemeData(
textStyle: TextStyle(
fontSize: CupertinoTheme.of(context).textTheme.textStyle.fontSize! - 2,
)
)
),
child: subtitle!,
),
],
),
),
), ),
), if (trailing != null)
if (trailing != null) Padding(
trailing!, padding: const EdgeInsets.all(8.0),
], child: trailing!,
),
],
),
); );
} }
} }

2
lib/components/select_train_suggestions/select_train_suggestions.dart

@ -1,4 +1,3 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.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_cupertino.dart';
@ -6,7 +5,6 @@ import 'package:info_tren/components/select_train_suggestions/select_train_sugge
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_material.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_material.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
import 'package:info_tren/utils/default_ui_design.dart';
class SelectTrainSuggestions extends ConsumerWidget { class SelectTrainSuggestions extends ConsumerWidget {
final List<TrainsResult> choices; final List<TrainsResult> choices;

5
lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart

@ -1,6 +1,7 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:info_tren/components/cupertino_divider.dart'; import 'package:info_tren/components/cupertino_divider.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
class SelectTrainSuggestionsCupertino extends SelectTrainSuggestionsShared { class SelectTrainSuggestionsCupertino extends SelectTrainSuggestionsShared {
@ -69,8 +70,8 @@ class OperatorAutocompleteTileCupertino extends OperatorAutocompleteTile {
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200), style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
Text( Text.rich(
"${train.rank} ${train.number}", trainIdSpan(rank: train.rank, number: train.number),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
], ],

5
lib/components/select_train_suggestions/select_train_suggestions_fluent.dart

@ -1,5 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
class SelectTrainSuggestionsFluent extends SelectTrainSuggestionsShared { class SelectTrainSuggestionsFluent extends SelectTrainSuggestionsShared {
@ -68,8 +69,8 @@ class OperatorAutocompleteTileFluent extends OperatorAutocompleteTile {
style: FluentTheme.of(context).typography.body?.copyWith(fontSize: 10, fontWeight: FontWeight.w200), style: FluentTheme.of(context).typography.body?.copyWith(fontSize: 10, fontWeight: FontWeight.w200),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
Text( Text.rich(
"${train.rank} ${train.number}", trainIdSpan(rank: train.rank, number: train.number),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
], ],

3
lib/components/select_train_suggestions/select_train_suggestions_material.dart

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
class SelectTrainSuggestionsMaterial extends SelectTrainSuggestionsShared { class SelectTrainSuggestionsMaterial extends SelectTrainSuggestionsShared {
@ -44,7 +45,7 @@ class OperatorAutocompleteTileMaterial extends OperatorAutocompleteTile {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
dense: true, dense: true,
title: Text("${train.rank} ${train.number}"), title: Text.rich(trainIdSpan(rank: train.rank, number: train.number)),
subtitle: Text(operatorName), subtitle: Text(operatorName),
onTap: () { onTap: () {
onTrainSelected(train.number); onTrainSelected(train.number);

2
lib/components/slim_app_bar.dart

@ -44,7 +44,7 @@ class SlimAppBar extends StatelessWidget {
title, title,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: style:
Theme.of(context).appBarTheme.textTheme?.bodySmall?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.bodyMedium?.color) ?? Theme.of(context).textTheme.titleSmall?.copyWith(color: Theme.of(context).textTheme.titleLarge?.color) ??
Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).textTheme.bodyMedium?.color), Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).textTheme.bodyMedium?.color),
), ),
), ),

39
lib/components/train_id_text_span.dart

@ -0,0 +1,39 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
TextSpan trainIdSpan({
required String rank,
required String number,
Locale? locale,
MouseCursor? mouseCursor,
void Function(PointerEnterEvent)? onEnter,
void Function(PointerExitEvent)? onExit,
GestureRecognizer? recognizer,
String? semanticsLabel,
bool? spellOut,
TextStyle? style,
}) => TextSpan(
children: [
TextSpan(
text: rank,
style: TextStyle(
inherit: true,
color: rank.startsWith('IC')
? const Color.fromARGB(255, 0, 255, 0)
: rank.startsWith('IR')
? const Color.fromARGB(255, 255, 0, 0)
: null,
),
),
const TextSpan(text: ' '),
TextSpan(text: number),
],
locale: locale,
mouseCursor: mouseCursor,
onEnter: onEnter,
onExit: onExit,
recognizer: recognizer,
semanticsLabel: semanticsLabel,
spellOut: spellOut,
style: style,
);

144
lib/main.dart

@ -1,5 +1,9 @@
import 'dart:io';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fluent_ui/fluent_ui.dart' as f; import 'package:fluent_ui/fluent_ui.dart' as f;
import 'package:flutter/cupertino.dart' as c; import 'package:flutter/cupertino.dart' as c;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' as m; import 'package:flutter/material.dart' as m;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -7,14 +11,18 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/about/about_page.dart'; import 'package:info_tren/pages/about/about_page.dart';
import 'package:info_tren/pages/main/main_page.dart'; import 'package:info_tren/pages/main/main_page.dart';
import 'package:info_tren/pages/settings/setings_page.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.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.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:timezone/data/latest.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized();
initializeTimeZones();
final sharedPreferences = await SharedPreferences.getInstance(); final sharedPreferences = await SharedPreferences.getInstance();
runApp( runApp(
ProviderScope( ProviderScope(
@ -33,6 +41,9 @@ Map<String, WidgetBuilder> get routes => {
AboutPage.routeName: (context) { AboutPage.routeName: (context) {
return const AboutPage(); return const AboutPage();
}, },
SettingsPage.routeName: (context) {
return const SettingsPage();
},
SelectTrainPage.routeName: (context) { SelectTrainPage.routeName: (context) {
return const SelectTrainPage(); return const SelectTrainPage();
}, },
@ -59,6 +70,36 @@ Map<String, WidgetBuilder> get routes => {
}, },
}; };
class DragFluentScrollBevahior extends f.FluentScrollBehavior {
const DragFluentScrollBevahior();
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
};
}
class DragCupertinoScrollBevahior extends c.CupertinoScrollBehavior {
const DragCupertinoScrollBevahior();
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
};
}
class DragMaterialScrollBevahior extends m.MaterialScrollBehavior {
const DragMaterialScrollBevahior();
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
};
}
class StartPoint extends ConsumerWidget { class StartPoint extends ConsumerWidget {
final String appTitle = 'Info Tren'; final String appTitle = 'Info Tren';
@ -68,49 +109,82 @@ class StartPoint extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider); final uiDesign = ref.watch(uiDesignProvider);
if (uiDesign == UiDesign.CUPERTINO) { if (uiDesign == UiDesign.CUPERTINO) {
return AnnotatedRegion( return DynamicColorBuilder(
value: const SystemUiOverlayStyle( builder: (lightScheme, darkScheme) {
statusBarBrightness: c.Brightness.dark, return AnnotatedRegion(
), value: const SystemUiOverlayStyle(
child: c.CupertinoApp( statusBarBrightness: c.Brightness.dark,
title: appTitle, ),
theme: c.CupertinoThemeData( child: c.CupertinoApp(
primaryColor: m.Colors.blue.shade600, title: appTitle,
brightness: c.Brightness.dark, theme: c.CupertinoThemeData(
// textTheme: CupertinoTextThemeData( primaryColor: darkScheme?.primary ?? m.Colors.blue.shade600,
// textStyle: TextStyle( brightness: c.Brightness.dark,
// fontFamily: 'Atkinson Hyperlegible', // textTheme: CupertinoTextThemeData(
// ), // textStyle: TextStyle(
// ), // fontFamily: 'Atkinson Hyperlegible',
), // ),
routes: routes, // ),
), ),
scrollBehavior: Platform.isLinux ? const DragCupertinoScrollBevahior() : null,
routes: routes,
),
);
}
); );
} }
else if (uiDesign == UiDesign.FLUENT) { else if (uiDesign == UiDesign.FLUENT) {
return f.FluentApp( return DynamicColorBuilder(
title: appTitle, builder: (lightScheme, darkScheme) {
theme: f.ThemeData( return f.FluentApp(
brightness: f.Brightness.dark, title: appTitle,
accentColor: f.Colors.blue, theme: f.FluentThemeData(
), brightness: f.Brightness.light,
routes: routes, accentColor: lightScheme != null ? f.AccentColor.swatch({
'normal': lightScheme.primary,
}) : f.Colors.blue,
),
darkTheme: f.FluentThemeData(
brightness: f.Brightness.dark,
accentColor: darkScheme != null ? f.AccentColor.swatch({
'normal': darkScheme.primary,
}) : f.Colors.blue,
),
routes: routes,
scrollBehavior: Platform.isLinux ? const DragFluentScrollBevahior() : const f.FluentScrollBehavior(),
);
}
); );
} }
else { else {
return m.MaterialApp( return DynamicColorBuilder(
title: appTitle, builder: (lightScheme, darkScheme) {
theme: m.ThemeData( lightScheme ??= m.ColorScheme.fromSwatch(
primarySwatch: m.Colors.blue, brightness: m.Brightness.light,
colorScheme: m.ColorScheme.fromSwatch( primarySwatch: m.Colors.blue,
);
darkScheme ??= m.ColorScheme.fromSwatch(
brightness: m.Brightness.dark, brightness: m.Brightness.dark,
primarySwatch: m.Colors.blue, primarySwatch: m.Colors.blue,
accentColor: m.Colors.blue.shade700, );
),
useMaterial3: true,
// fontFamily: 'Atkinson Hyperlegible', return m.MaterialApp(
), title: appTitle,
routes: routes, theme: m.ThemeData(
colorScheme: lightScheme,
useMaterial3: true,
// fontFamily: 'Atkinson Hyperlegible',
),
darkTheme: m.ThemeData(
colorScheme: darkScheme,
useMaterial3: true,
// fontFamily: 'Atkinson Hyperlegible',
),
scrollBehavior: Platform.isLinux ? const DragMaterialScrollBevahior() : null,
routes: routes,
);
}
); );
} }
} }

7
lib/models.dart

@ -4,10 +4,11 @@ export 'package:info_tren/models/station_data.dart';
export 'package:info_tren/models/station_status.dart'; export 'package:info_tren/models/station_status.dart';
export 'package:info_tren/models/station_train.dart'; export 'package:info_tren/models/station_train.dart';
export 'package:info_tren/models/stations_result.dart'; export 'package:info_tren/models/stations_result.dart';
export 'package:info_tren/models/train_data.dart' hide State; export 'package:info_tren/models/train_data.dart';
export 'package:info_tren/models/trains_result.dart'; export 'package:info_tren/models/trains_result.dart';
export 'package:info_tren/models/ui_design.dart'; export 'package:info_tren/models/ui_design.dart';
export 'package:info_tren/models/ui_timezone.dart';
import 'package:info_tren/models/train_data.dart' show State; import 'package:info_tren/models/train_data.dart' show TrainDataStatusState;
typedef TrainDataState = State; typedef TrainDataState = TrainDataStatusState;

10
lib/models/changelog_entry.dart

@ -4,8 +4,16 @@ class ChangelogEntry {
final ChangelogVersion version; final ChangelogVersion version;
final String description; final String description;
final Uri? apkLink; final Uri? apkLink;
final Uri? linuxLink;
final Uri? windowsLink;
ChangelogEntry({required this.version, required this.description, this.apkLink}); ChangelogEntry({
required this.version,
required this.description,
this.apkLink,
this.linuxLink,
this.windowsLink,
});
factory ChangelogEntry.fromTextBlock(String text) { factory ChangelogEntry.fromTextBlock(String text) {
final lines = text.split(RegExp(r'(\r?\n)+')); final lines = text.split(RegExp(r'(\r?\n)+'));

2
lib/models/station_arrdep.freezed.dart

@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'station_arrdep.dart'; part of 'station_arrdep.dart';

4
lib/models/station_data.freezed.dart

@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'station_data.dart'; part of 'station_data.dart';
@ -158,6 +158,7 @@ class _$_StationData implements _StationData {
List<StationArrDep>? get arrivals { List<StationArrDep>? get arrivals {
final value = _arrivals; final value = _arrivals;
if (value == null) return null; if (value == null) return null;
if (_arrivals is EqualUnmodifiableListView) return _arrivals;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value); return EqualUnmodifiableListView(value);
} }
@ -167,6 +168,7 @@ class _$_StationData implements _StationData {
List<StationArrDep>? get departures { List<StationArrDep>? get departures {
final value = _departures; final value = _departures;
if (value == null) return null; if (value == null) return null;
if (_departures is EqualUnmodifiableListView) return _departures;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value); return EqualUnmodifiableListView(value);
} }

2
lib/models/station_status.freezed.dart

@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'station_status.dart'; part of 'station_status.dart';

3
lib/models/station_train.freezed.dart

@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'station_train.dart'; part of 'station_train.dart';
@ -189,6 +189,7 @@ class _$_StationTrain implements _StationTrain {
List<String>? get route { List<String>? get route {
final value = _route; final value = _route;
if (value == null) return null; if (value == null) return null;
if (_route is EqualUnmodifiableListView) return _route;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value); return EqualUnmodifiableListView(value);
} }

3
lib/models/stations_result.freezed.dart

@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'stations_result.dart'; part of 'stations_result.dart';
@ -121,6 +121,7 @@ class _$_StationsResult implements _StationsResult {
List<String>? get stoppedAtBy { List<String>? get stoppedAtBy {
final value = _stoppedAtBy; final value = _stoppedAtBy;
if (value == null) return null; if (value == null) return null;
if (_stoppedAtBy is EqualUnmodifiableListView) return _stoppedAtBy;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value); return EqualUnmodifiableListView(value);
} }

373
lib/models/train_data.dart

@ -4,181 +4,118 @@
import 'dart:convert'; import 'dart:convert';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'train_data.freezed.dart';
part 'train_data.g.dart';
TrainData trainDataFromJson(String str) => TrainData.fromJson(json.decode(str)); TrainData trainDataFromJson(String str) => TrainData.fromJson(json.decode(str));
String trainDataToJson(TrainData data) => json.encode(data.toJson()); String trainDataToJson(TrainData data) => json.encode(data.toJson());
/// Results of scrapping InfoFer website for train info /// Results of scrapping InfoFer website for train info
class TrainData { @freezed
TrainData({ class TrainData with _$TrainData {
required this.date, const TrainData._();
required this.number,
required this.operator, const factory TrainData({
required this.rank, required String rank,
required this.route, required String number,
required this.stations, required String date,
this.status, required String operator,
}); required List<TrainDataGroup> groups,
}) = _TrainData;
final String date;
final String number; factory TrainData.fromJson(Map<String, dynamic> json) => _$TrainDataFromJson(json);
final String operator;
final String rank; TrainDataGroup get idealGroup {
final Route route; var result = groups.first;
final List<Station> stations;
final TrainDataStatus? status; for (final group in groups) {
if (result.stations.map((s) => s.linkName).toSet().difference(group.stations.map((s) => s.linkName).toSet()).isEmpty) {
factory TrainData.fromJson(Map<String, dynamic> json) => TrainData( result = group;
date: json["date"], }
number: json["number"], }
operator: json["operator"],
rank: json["rank"],
route: Route.fromJson(json["route"]),
stations: List<Station>.from(
json["stations"].map((x) => Station.fromJson(x))),
status: json["status"] == null
? null
: TrainDataStatus.fromJson(json["status"]),
);
Map<String, dynamic> toJson() => { return result;
"date": date, }
"number": number,
"operator": operator, List<TrainDataStation> get stations => idealGroup.stations;
"rank": rank, TrainDataRoute get route => idealGroup.route;
"route": route.toJson(), TrainDataStatus? get status => idealGroup.status;
"stations": List<dynamic>.from(stations.map((x) => x.toJson())),
"status": status?.toJson(),
};
} }
/// Route of the train @freezed
class Route { class TrainDataGroup with _$TrainDataGroup {
Route({ const factory TrainDataGroup({
required this.from, required TrainDataRoute route,
required this.to, required List<TrainDataStation> stations,
}); TrainDataStatus? status,
}) = _TrainDataGroup;
final String from;
final String to; factory TrainDataGroup.fromJson(Map<String, dynamic> json) => _$TrainDataGroupFromJson(json);
factory Route.fromJson(Map<String, dynamic> json) => Route(
from: json["from"],
to: json["to"],
);
Map<String, dynamic> toJson() => {
"from": from,
"to": to,
};
} }
class Station { /// Route of the train
Station({ @freezed
this.arrival, class TrainDataRoute with _$TrainDataRoute {
this.departure, const factory TrainDataRoute({
required this.km, required String from,
required this.name, required String to,
this.platform, }) = _TrainDataRoute;
this.stoppingTime,
}); factory TrainDataRoute.fromJson(Map<String, dynamic> json) => _$TrainDataRouteFromJson(json);
final StationArrDepTime? arrival;
final StationArrDepTime? departure;
final int km;
final String name;
final String? platform;
final int? stoppingTime;
factory Station.fromJson(Map<String, dynamic> 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"],
stoppingTime:
json["stoppingTime"],
);
Map<String, dynamic> toJson() => {
"arrival": arrival?.toJson(),
"departure": departure?.toJson(),
"km": km,
"name": name,
"platform": platform,
"stoppingTime": stoppingTime,
};
} }
class StationArrDepTime { @freezed
StationArrDepTime({ class TrainDataStation with _$TrainDataStation {
required this.scheduleTime, const factory TrainDataStation({
this.status, required String name,
}); required String linkName,
required int km,
final DateTime scheduleTime; int? stoppingTime,
final StationArrDepTimeStatus? status; String? platform,
StationArrDepTime? arrival,
factory StationArrDepTime.fromJson(Map<String, dynamic> json) => StationArrDepTime? departure,
StationArrDepTime( @TrainDataNoteConverter()
scheduleTime: DateTime.parse(json["scheduleTime"] as String), required List<TrainDataNote> notes,
status: json["status"] == null ? null : StationArrDepTimeStatus.fromJson(json["status"]), }) = _TrainDataStation;
);
factory TrainDataStation.fromJson(Map<String, dynamic> json) => _$TrainDataStationFromJson(json);
Map<String, dynamic> toJson() => {
"scheduleTime": scheduleTime.toIso8601String(),
"status": status?.toJson(),
};
} }
class StationArrDepTimeStatus { @freezed
StationArrDepTimeStatus({ class StationArrDepTime with _$StationArrDepTime {
required this.delay, const factory StationArrDepTime({
required this.real, required DateTime scheduleTime,
}); StationArrDepTimeStatus? status,
}) = _StationArrDepTime;
final int delay; factory StationArrDepTime.fromJson(Map<String, dynamic> json) => _$StationArrDepTimeFromJson(json);
final bool real; }
factory StationArrDepTimeStatus.fromJson(Map<String, dynamic> json) => @freezed
StationArrDepTimeStatus( class StationArrDepTimeStatus with _$StationArrDepTimeStatus {
delay: json["delay"], const factory StationArrDepTimeStatus({
real: json["real"], required int delay,
); required bool real,
required bool cancelled,
}) = _StationArrDepTimeStatus;
Map<String, dynamic> toJson() => { factory StationArrDepTimeStatus.fromJson(Map<String, dynamic> json) => _$StationArrDepTimeStatusFromJson(json);
"delay": delay,
"real": real,
};
} }
class TrainDataStatus { @freezed
TrainDataStatus({ class TrainDataStatus with _$TrainDataStatus {
required this.delay, const TrainDataStatus._();
required this.state,
required this.station,
});
final int delay;
final State state;
final String station;
factory TrainDataStatus.fromJson(Map<String, dynamic> json) => const factory TrainDataStatus({
TrainDataStatus( required int delay,
delay: json["delay"], required String station,
state: stateValues.map[json["state"]]!, required TrainDataStatusState state,
station: json["station"], }) = _TrainDataStatus;
);
Map<String, dynamic> toJson() => { factory TrainDataStatus.fromJson(Map<String, dynamic> json) => _$TrainDataStatusFromJson(json);
"delay": delay,
"state": stateValues.reverse[state],
"station": station,
};
@override @override
String toString() { String toString() {
@ -190,38 +127,118 @@ class TrainDataStatus {
result += '${delay.abs()} min'; result += '${delay.abs()} min';
} }
result += ' la '; result += ' la ';
switch (state) { result += switch (state) {
case State.PASSING: TrainDataStatusState.passing => 'trecerea fără oprire prin',
result += 'trecerea fără oprire prin'; TrainDataStatusState.arrival => 'sosirea în',
break; TrainDataStatusState.departure => 'plecarea din',
case State.ARRIVAL: };
result += 'sosirea în';
break;
case State.DEPARTURE:
result += 'plecarea din';
break;
}
result += station; result += station;
return result; return result;
} }
} }
enum State { PASSING, ARRIVAL, DEPARTURE } enum TrainDataStatusState { passing, arrival, departure }
final stateValues = EnumValues({ abstract class TrainDataNote {
"arrival": State.ARRIVAL, final String kind;
"departure": State.DEPARTURE,
"passing": State.PASSING
});
class EnumValues<T> { const TrainDataNote({required this.kind});
Map<String, T> map;
Map<T, String>? reverseMap;
EnumValues(this.map); Map<String, dynamic> toJson() => {
"kind": kind,
};
}
class TrainDataNoteConverter implements JsonConverter<TrainDataNote, Map<String, dynamic>> {
const TrainDataNoteConverter();
@override
TrainDataNote fromJson(Map<String, dynamic> json) {
return switch(json['kind']) {
'trainNumberChange' => TrainDataNoteTrainNumberChange.fromJson(json),
'departsAs' => TrainDataNoteDepartsAs.fromJson(json),
'detachingWagons' => TrainDataNoteDetachingWagons.fromJson(json),
'receivingWagons' => TrainDataNoteReceivingWagons.fromJson(json),
_ => TrainDataNoteUnknown.fromJson(json),
};
}
Map<T, String> get reverse { @override
reverseMap ??= map.map((k, v) => MapEntry(v, k)); Map<String, dynamic> toJson(TrainDataNote object) {
return reverseMap!; return object.toJson();
} }
} }
@freezed
class TrainDataNoteTrainNumberChange with _$TrainDataNoteTrainNumberChange implements TrainDataNote {
@Implements<TrainDataNote>()
const factory TrainDataNoteTrainNumberChange({
// base
@Default("trainNumberChange")
String kind,
// impl
required String rank,
required String number,
}) = _TrainDataNoteTrainNumberChange;
factory TrainDataNoteTrainNumberChange.fromJson(Map<String, dynamic> json) => _$TrainDataNoteTrainNumberChangeFromJson(json);
}
@freezed
class TrainDataNoteDepartsAs with _$TrainDataNoteDepartsAs implements TrainDataNote {
@Implements<TrainDataNote>()
const factory TrainDataNoteDepartsAs({
// base
@Default("departsAs")
String kind,
// impl
required String rank,
required String number,
required DateTime departureDate,
}) = _TrainDataNoteDepartsAs;
factory TrainDataNoteDepartsAs.fromJson(Map<String, dynamic> json) => _$TrainDataNoteDepartsAsFromJson(json);
}
@freezed
class TrainDataNoteDetachingWagons with _$TrainDataNoteDetachingWagons implements TrainDataNote {
@Implements<TrainDataNote>()
const factory TrainDataNoteDetachingWagons({
// base
@Default("detachingWagons")
String kind,
// impl
required String station,
}) = _TrainDataNoteDetachingWagons;
factory TrainDataNoteDetachingWagons.fromJson(Map<String, dynamic> json) => _$TrainDataNoteDetachingWagonsFromJson(json);
}
@freezed
class TrainDataNoteReceivingWagons with _$TrainDataNoteReceivingWagons implements TrainDataNote {
@Implements<TrainDataNote>()
const factory TrainDataNoteReceivingWagons({
// base
@Default("receivingWagons")
String kind,
// impl
required String station,
}) = _TrainDataNoteReceivingWagons;
factory TrainDataNoteReceivingWagons.fromJson(Map<String, dynamic> json) => _$TrainDataNoteReceivingWagonsFromJson(json);
}
@freezed
class TrainDataNoteUnknown with _$TrainDataNoteUnknown implements TrainDataNote {
@Implements<TrainDataNote>()
const factory TrainDataNoteUnknown({
// base
required String kind,
// impl
required Map<String, dynamic> extra,
}) = _TrainDataNoteUnknown;
factory TrainDataNoteUnknown.fromJson(Map<String, dynamic> json) => TrainDataNoteUnknown(
kind: json['kind'],
extra: Map.from(json)..remove('kind'),
);
}

2365
lib/models/train_data.freezed.dart

File diff suppressed because it is too large Load Diff

217
lib/models/train_data.g.dart

@ -0,0 +1,217 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'train_data.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_TrainData _$$_TrainDataFromJson(Map<String, dynamic> json) => _$_TrainData(
rank: json['rank'] as String,
number: json['number'] as String,
date: json['date'] as String,
operator: json['operator'] as String,
groups: (json['groups'] as List<dynamic>)
.map((e) => TrainDataGroup.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$_TrainDataToJson(_$_TrainData instance) =>
<String, dynamic>{
'rank': instance.rank,
'number': instance.number,
'date': instance.date,
'operator': instance.operator,
'groups': instance.groups,
};
_$_TrainDataGroup _$$_TrainDataGroupFromJson(Map<String, dynamic> json) =>
_$_TrainDataGroup(
route: TrainDataRoute.fromJson(json['route'] as Map<String, dynamic>),
stations: (json['stations'] as List<dynamic>)
.map((e) => TrainDataStation.fromJson(e as Map<String, dynamic>))
.toList(),
status: json['status'] == null
? null
: TrainDataStatus.fromJson(json['status'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$_TrainDataGroupToJson(_$_TrainDataGroup instance) =>
<String, dynamic>{
'route': instance.route,
'stations': instance.stations,
'status': instance.status,
};
_$_TrainDataRoute _$$_TrainDataRouteFromJson(Map<String, dynamic> json) =>
_$_TrainDataRoute(
from: json['from'] as String,
to: json['to'] as String,
);
Map<String, dynamic> _$$_TrainDataRouteToJson(_$_TrainDataRoute instance) =>
<String, dynamic>{
'from': instance.from,
'to': instance.to,
};
_$_TrainDataStation _$$_TrainDataStationFromJson(Map<String, dynamic> json) =>
_$_TrainDataStation(
name: json['name'] as String,
linkName: json['linkName'] as String,
km: json['km'] as int,
stoppingTime: json['stoppingTime'] as int?,
platform: json['platform'] as String?,
arrival: json['arrival'] == null
? null
: StationArrDepTime.fromJson(json['arrival'] as Map<String, dynamic>),
departure: json['departure'] == null
? null
: StationArrDepTime.fromJson(
json['departure'] as Map<String, dynamic>),
notes: (json['notes'] as List<dynamic>)
.map((e) => const TrainDataNoteConverter()
.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$_TrainDataStationToJson(_$_TrainDataStation instance) =>
<String, dynamic>{
'name': instance.name,
'linkName': instance.linkName,
'km': instance.km,
'stoppingTime': instance.stoppingTime,
'platform': instance.platform,
'arrival': instance.arrival,
'departure': instance.departure,
'notes':
instance.notes.map(const TrainDataNoteConverter().toJson).toList(),
};
_$_StationArrDepTime _$$_StationArrDepTimeFromJson(Map<String, dynamic> json) =>
_$_StationArrDepTime(
scheduleTime: DateTime.parse(json['scheduleTime'] as String),
status: json['status'] == null
? null
: StationArrDepTimeStatus.fromJson(
json['status'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$_StationArrDepTimeToJson(
_$_StationArrDepTime instance) =>
<String, dynamic>{
'scheduleTime': instance.scheduleTime.toIso8601String(),
'status': instance.status,
};
_$_StationArrDepTimeStatus _$$_StationArrDepTimeStatusFromJson(
Map<String, dynamic> json) =>
_$_StationArrDepTimeStatus(
delay: json['delay'] as int,
real: json['real'] as bool,
cancelled: json['cancelled'] as bool,
);
Map<String, dynamic> _$$_StationArrDepTimeStatusToJson(
_$_StationArrDepTimeStatus instance) =>
<String, dynamic>{
'delay': instance.delay,
'real': instance.real,
'cancelled': instance.cancelled,
};
_$_TrainDataStatus _$$_TrainDataStatusFromJson(Map<String, dynamic> json) =>
_$_TrainDataStatus(
delay: json['delay'] as int,
station: json['station'] as String,
state: $enumDecode(_$TrainDataStatusStateEnumMap, json['state']),
);
Map<String, dynamic> _$$_TrainDataStatusToJson(_$_TrainDataStatus instance) =>
<String, dynamic>{
'delay': instance.delay,
'station': instance.station,
'state': _$TrainDataStatusStateEnumMap[instance.state]!,
};
const _$TrainDataStatusStateEnumMap = {
TrainDataStatusState.passing: 'passing',
TrainDataStatusState.arrival: 'arrival',
TrainDataStatusState.departure: 'departure',
};
_$_TrainDataNoteTrainNumberChange _$$_TrainDataNoteTrainNumberChangeFromJson(
Map<String, dynamic> json) =>
_$_TrainDataNoteTrainNumberChange(
kind: json['kind'] as String? ?? "trainNumberChange",
rank: json['rank'] as String,
number: json['number'] as String,
);
Map<String, dynamic> _$$_TrainDataNoteTrainNumberChangeToJson(
_$_TrainDataNoteTrainNumberChange instance) =>
<String, dynamic>{
'kind': instance.kind,
'rank': instance.rank,
'number': instance.number,
};
_$_TrainDataNoteDepartsAs _$$_TrainDataNoteDepartsAsFromJson(
Map<String, dynamic> json) =>
_$_TrainDataNoteDepartsAs(
kind: json['kind'] as String? ?? "departsAs",
rank: json['rank'] as String,
number: json['number'] as String,
departureDate: DateTime.parse(json['departureDate'] as String),
);
Map<String, dynamic> _$$_TrainDataNoteDepartsAsToJson(
_$_TrainDataNoteDepartsAs instance) =>
<String, dynamic>{
'kind': instance.kind,
'rank': instance.rank,
'number': instance.number,
'departureDate': instance.departureDate.toIso8601String(),
};
_$_TrainDataNoteDetachingWagons _$$_TrainDataNoteDetachingWagonsFromJson(
Map<String, dynamic> json) =>
_$_TrainDataNoteDetachingWagons(
kind: json['kind'] as String? ?? "detachingWagons",
station: json['station'] as String,
);
Map<String, dynamic> _$$_TrainDataNoteDetachingWagonsToJson(
_$_TrainDataNoteDetachingWagons instance) =>
<String, dynamic>{
'kind': instance.kind,
'station': instance.station,
};
_$_TrainDataNoteReceivingWagons _$$_TrainDataNoteReceivingWagonsFromJson(
Map<String, dynamic> json) =>
_$_TrainDataNoteReceivingWagons(
kind: json['kind'] as String? ?? "receivingWagons",
station: json['station'] as String,
);
Map<String, dynamic> _$$_TrainDataNoteReceivingWagonsToJson(
_$_TrainDataNoteReceivingWagons instance) =>
<String, dynamic>{
'kind': instance.kind,
'station': instance.station,
};
_$_TrainDataNoteUnknown _$$_TrainDataNoteUnknownFromJson(
Map<String, dynamic> json) =>
_$_TrainDataNoteUnknown(
kind: json['kind'] as String,
extra: json['extra'] as Map<String, dynamic>,
);
Map<String, dynamic> _$$_TrainDataNoteUnknownToJson(
_$_TrainDataNoteUnknown instance) =>
<String, dynamic>{
'kind': instance.kind,
'extra': instance.extra,
};

2
lib/models/trains_result.freezed.dart

@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'trains_result.dart'; part of 'trains_result.dart';

8
lib/models/ui_design.dart

@ -4,6 +4,14 @@ enum UiDesign {
FLUENT, FLUENT,
} }
extension UIName on UiDesign {
String get userInterfaceName => (const {
UiDesign.MATERIAL: 'Material',
UiDesign.CUPERTINO: 'Cupertino',
UiDesign.FLUENT: 'Fluent',
})[this]!;
}
class UnmatchedUiDesignException implements Exception { class UnmatchedUiDesignException implements Exception {
final UiDesign uiDesign; final UiDesign uiDesign;

94
lib/models/ui_timezone.dart

@ -0,0 +1,94 @@
import 'package:timezone/timezone.dart' as tz;
enum UiTimeZoneType {
ro,
local,
utc,
iana,
}
extension UITimeZoneTypeName on UiTimeZoneType {
String get userInterfaceName => (const {
UiTimeZoneType.iana: 'Fus orar IANA',
UiTimeZoneType.local: 'Local',
UiTimeZoneType.ro: 'România',
UiTimeZoneType.utc: 'UTC',
})[this]!;
}
const Map<UiTimeZoneType, UiTimeZone Function(String)> fromSerStringConstructors = {
UiTimeZoneType.ro: RoUiTimeZone.fromSerString,
UiTimeZoneType.local: LocalUiTimeZone.fromSerString,
UiTimeZoneType.utc: UtcUiTimeZone.fromSerString,
UiTimeZoneType.iana: IanaUiTimeZone.fromSerString,
};
abstract class UiTimeZone {
final UiTimeZoneType type;
const UiTimeZone({required this.type});
DateTime convertDateTime(DateTime dt);
factory UiTimeZone.fromSerString(String ser) {
final arr = ser.split('\n');
return fromSerStringConstructors.map((key, value) => MapEntry(key.name, value))[arr[0]]!(ser);
}
String toSerString() {
return '${type.name}\n';
}
}
class RoUiTimeZone extends UiTimeZone {
static final roTz = tz.getLocation('Europe/Bucharest');
const RoUiTimeZone() : super(type: UiTimeZoneType.ro);
factory RoUiTimeZone.fromSerString(String ser) => const RoUiTimeZone();
@override
DateTime convertDateTime(DateTime dt) {
return tz.TZDateTime.from(dt, roTz);
}
}
class LocalUiTimeZone extends UiTimeZone {
const LocalUiTimeZone() : super(type: UiTimeZoneType.local);
factory LocalUiTimeZone.fromSerString(String ser) => LocalUiTimeZone();
@override
DateTime convertDateTime(DateTime dt) => dt.toLocal();
}
class UtcUiTimeZone extends UiTimeZone {
const UtcUiTimeZone() : super(type: UiTimeZoneType.utc);
factory UtcUiTimeZone.fromSerString(String ser) => UtcUiTimeZone();
@override
DateTime convertDateTime(DateTime dt) => dt.toUtc();
}
class IanaUiTimeZone extends UiTimeZone {
late final tz.Location location;
IanaUiTimeZone({required String ianaName}): super(type: UiTimeZoneType.iana) {
location = tz.getLocation(ianaName);
}
factory IanaUiTimeZone.fromSerString(String ser) => IanaUiTimeZone(
ianaName: ser.split('\n').skip(1).join('\n'),
);
@override
DateTime convertDateTime(DateTime dt) {
return tz.TZDateTime.from(dt, location);
}
@override
String toSerString() {
return '${type.name}\n${location.name}';
}
}

5
lib/pages/about/about_page.dart

@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/api/releases.dart'; import 'package:info_tren/api/releases.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/about/about_page_cupertino.dart'; import 'package:info_tren/pages/about/about_page_cupertino.dart';
import 'package:info_tren/pages/about/about_page_fluent.dart';
import 'package:info_tren/pages/about/about_page_material.dart'; import 'package:info_tren/pages/about/about_page_material.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
@ -21,6 +22,8 @@ class AboutPage extends ConsumerWidget {
return const AboutPageMaterial(); return const AboutPageMaterial();
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return const AboutPageCupertino(); return const AboutPageCupertino();
case UiDesign.FLUENT:
return const AboutPageFluent();
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
@ -31,7 +34,7 @@ abstract class AboutPageShared extends StatefulWidget {
const AboutPageShared({super.key}); const AboutPageShared({super.key});
} }
abstract class AboutPageState extends State<AboutPageShared> { abstract class AboutPageState<T extends AboutPageShared> extends State<T> {
static const String download = String.fromEnvironment('DOWNLOAD'); static const String download = String.fromEnvironment('DOWNLOAD');
final String pageTitle = 'Despre aplicație'; final String pageTitle = 'Despre aplicație';

4
lib/pages/about/about_page_cupertino.dart

@ -3,14 +3,14 @@ import 'package:info_tren/components/cupertino_divider.dart';
import 'package:info_tren/pages/about/about_page.dart'; import 'package:info_tren/pages/about/about_page.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class AboutPageCupertino extends StatefulWidget { class AboutPageCupertino extends AboutPageShared {
const AboutPageCupertino({super.key}); const AboutPageCupertino({super.key});
@override @override
State<AboutPageShared> createState() => AboutPageStateCupertino(); State<AboutPageShared> createState() => AboutPageStateCupertino();
} }
class AboutPageStateCupertino extends AboutPageState { class AboutPageStateCupertino extends AboutPageState<AboutPageCupertino> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(

198
lib/pages/about/about_page_fluent.dart

@ -0,0 +1,198 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:info_tren/pages/about/about_page.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutPageFluent extends AboutPageShared {
const AboutPageFluent({super.key});
@override
State<AboutPageShared> createState() => AboutPageStateFluent();
}
class AboutPageStateFluent extends AboutPageState<AboutPageFluent> {
@override
Widget build(BuildContext context) {
return NavigationView(
appBar: NavigationAppBar(
title: Text(pageTitle),
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
'Info Tren',
style: FluentTheme.of(context).typography.display,
),
),
if (packageInfo != null)
Center(
child: Text(
packageInfo!.packageName,
style: FluentTheme.of(context).typography.caption,
),
),
// ListTile(
// title: Text(versionTitleText),
// subtitle: localChangelog.isEmpty ? null : Text(localChangelog.first.title),
// ),
const Divider(),
for (final log in mergedChangelogs) ...[
Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
log.version.toString(),
style: FluentTheme.of(context).typography.title,
),
),
if (localChangelog.isNotEmpty && log.version == localChangelog.first.version)
Container(
decoration: BoxDecoration(
border: Border.all(
color: FluentTheme.of(context).inactiveColor,
width: 1,
),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
currentVersionText,
style: const TextStyle(
inherit: true,
),
),
),
),
if (remoteChangelog.isNotEmpty && log.version == remoteChangelog.first.version && (localChangelog.isEmpty || localChangelog.first.version != log.version))
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.green,
width: 1,
),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
latestVersionText,
style: TextStyle(
inherit: true,
color: Colors.green,
),
),
),
),
if (AboutPageState.download == 'apk' && log.apkLink != null)
GestureDetector(
onSecondaryTap: () {
Clipboard.setData(ClipboardData(text: log.apkLink!.toString()));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text('Link copied to clipboard'),
// ));
},
onLongPress: () {
Clipboard.setData(ClipboardData(text: log.apkLink!.toString()));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text('Link copied to clipboard'),
// ));
},
onTap: () {
launchUrl(
log.apkLink!,
mode: LaunchMode.externalApplication,
);
},
behavior: HitTestBehavior.translucent,
child: const Tooltip(
message: 'Download APK',
child: Padding(
padding: EdgeInsets.all(4),
child: Icon(FluentIcons.download),
),
),
),
if (AboutPageState.download == 'linux' && log.linuxLink != null)
GestureDetector(
onSecondaryTap: () {
Clipboard.setData(ClipboardData(text: log.linuxLink!.toString()));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text('Link copied to clipboard'),
// ));
},
onLongPress: () {
Clipboard.setData(ClipboardData(text: log.linuxLink!.toString()));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text('Link copied to clipboard'),
// ));
},
onTap: () {
launchUrl(
log.linuxLink!,
mode: LaunchMode.externalApplication,
);
},
behavior: HitTestBehavior.translucent,
child: const Tooltip(
message: 'Download Linux ZIP',
child: Padding(
padding: EdgeInsets.all(4),
child: Icon(FluentIcons.download),
),
),
),
if (AboutPageState.download == 'windows' && log.windowsLink != null)
GestureDetector(
onSecondaryTap: () {
Clipboard.setData(ClipboardData(text: log.windowsLink!.toString()));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text('Link copied to clipboard'),
// ));
},
onLongPress: () {
Clipboard.setData(ClipboardData(text: log.windowsLink!.toString()));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text('Link copied to clipboard'),
// ));
},
onTap: () {
launchUrl(
log.windowsLink!,
mode: LaunchMode.externalApplication,
);
},
behavior: HitTestBehavior.translucent,
child: const Tooltip(
message: 'Download Windows App ZIP',
child: Padding(
padding: EdgeInsets.all(4),
child: Icon(FluentIcons.download),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RichText(
text: TextSpan(
text: log.description,
),
),
),
],
],
),
),
);
}
}

64
lib/pages/about/about_page_material.dart

@ -3,14 +3,14 @@ import 'package:flutter/services.dart';
import 'package:info_tren/pages/about/about_page.dart'; import 'package:info_tren/pages/about/about_page.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class AboutPageMaterial extends StatefulWidget { class AboutPageMaterial extends AboutPageShared {
const AboutPageMaterial({super.key}); const AboutPageMaterial({super.key});
@override @override
State<AboutPageShared> createState() => AboutPageStateMaterial(); State<AboutPageShared> createState() => AboutPageStateMaterial();
} }
class AboutPageStateMaterial extends AboutPageState { class AboutPageStateMaterial extends AboutPageState<AboutPageMaterial> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -121,6 +121,64 @@ class AboutPageStateMaterial extends AboutPageState {
), ),
), ),
), ),
if (AboutPageState.download == 'linux' && log.linuxLink != null)
GestureDetector(
onSecondaryTap: () {
Clipboard.setData(ClipboardData(text: log.linuxLink!.toString()));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Link copied to clipboard'),
));
},
onLongPress: () {
Clipboard.setData(ClipboardData(text: log.linuxLink!.toString()));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Link copied to clipboard'),
));
},
onTap: () {
launchUrl(
log.linuxLink!,
mode: LaunchMode.externalApplication,
);
},
behavior: HitTestBehavior.translucent,
child: const Tooltip(
message: 'Download Linux ZIP',
child: Padding(
padding: EdgeInsets.all(4),
child: Icon(Icons.download),
),
),
),
if (AboutPageState.download == 'windows' && log.windowsLink != null)
GestureDetector(
onSecondaryTap: () {
Clipboard.setData(ClipboardData(text: log.windowsLink!.toString()));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Link copied to clipboard'),
));
},
onLongPress: () {
Clipboard.setData(ClipboardData(text: log.windowsLink!.toString()));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Link copied to clipboard'),
));
},
onTap: () {
launchUrl(
log.windowsLink!,
mode: LaunchMode.externalApplication,
);
},
behavior: HitTestBehavior.translucent,
child: const Tooltip(
message: 'Download Windows App ZIP',
child: Padding(
padding: EdgeInsets.all(4),
child: Icon(Icons.download),
),
),
),
], ],
), ),
), ),
@ -138,4 +196,4 @@ class AboutPageStateMaterial extends AboutPageState {
), ),
); );
} }
} }

13
lib/pages/main/main_page.dart

@ -5,6 +5,7 @@ import 'package:info_tren/pages/about/about_page.dart';
import 'package:info_tren/pages/main/main_page_cupertino.dart'; import 'package:info_tren/pages/main/main_page_cupertino.dart';
import 'package:info_tren/pages/main/main_page_fluent.dart'; import 'package:info_tren/pages/main/main_page_fluent.dart';
import 'package:info_tren/pages/main/main_page_material.dart'; import 'package:info_tren/pages/main/main_page_material.dart';
import 'package:info_tren/pages/settings/setings_page.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -36,6 +37,12 @@ abstract class MainPageShared extends StatelessWidget {
const MainPageShared({super.key}); const MainPageShared({super.key});
List<MainPageAction> get popupMenu => [ List<MainPageAction> get popupMenu => [
MainPageAction(
name: 'Setări',
action: (context) {
Navigator.of(context).pushNamed(SettingsPage.routeName);
},
),
MainPageAction( MainPageAction(
name: 'Despre aplicație', name: 'Despre aplicație',
action: (context) { action: (context) {
@ -85,5 +92,9 @@ class MainPageAction {
final String? description; final String? description;
final void Function(BuildContext context)? action; final void Function(BuildContext context)? action;
MainPageAction({required this.name, this.action, this.description}); MainPageAction({
required this.name,
this.action,
this.description,
});
} }

16
lib/pages/main/main_page_fluent.dart

@ -10,6 +10,22 @@ class MainPageFluent extends MainPageShared {
appBar: NavigationAppBar( appBar: NavigationAppBar(
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: Text(pageTitle), title: Text(pageTitle),
actions: Row(
mainAxisSize: MainAxisSize.min,
children: popupMenu.map((i) => Center(
child: SizedBox(
width: 32,
height: 32,
child: IconButton(
icon: Icon({
'Setări': FluentIcons.settings,
'Despre aplicație': FluentIcons.info,
}[i.name]),
onPressed: i.action != null ? () => i.action!(context) : null,
),
),
)).toList(),
),
// centerTitle: true, // centerTitle: true,
// actions: [ // actions: [
// PopupMenuButton<int>( // PopupMenuButton<int>(

38
lib/pages/settings/setings_page.dart

@ -0,0 +1,38 @@
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/models.dart';
import 'package:info_tren/pages/settings/settings_page_cupertino.dart';
import 'package:info_tren/pages/settings/settings_page_fluent.dart';
import 'package:info_tren/pages/settings/settings_page_material.dart';
import 'package:info_tren/providers.dart';
class SettingsPage extends ConsumerWidget {
const SettingsPage({super.key,});
static const String routeName = '/settings';
@override
Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider);
switch (uiDesign) {
case UiDesign.MATERIAL:
return const SettingsPageMaterial();
case UiDesign.CUPERTINO:
return const SettingsPageCupertino();
case UiDesign.FLUENT:
return const SettingsPageFluent();
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}
abstract class SettingsPageShared extends StatelessWidget {
final String pageTitle = 'Setări';
final String appearanceTitle = 'Aspect';
final String timeZoneTitle = 'Fus orar';
const SettingsPageShared({super.key});
}

96
lib/pages/settings/settings_page_cupertino.dart

@ -0,0 +1,96 @@
import 'package:flutter/cupertino.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/models.dart';
import 'package:info_tren/pages/settings/setings_page.dart';
import 'package:info_tren/providers.dart';
class SettingsPageCupertino extends SettingsPageShared {
const SettingsPageCupertino({super.key,});
Future<T?> singleChoice<T>({required BuildContext context, required List<T> choices, required String Function(T) labelBuilder, String? title}) async {
return await showCupertinoModalPopup<T>(
context: context,
builder: (context) {
return CupertinoActionSheet(
title: title != null ? Text(title) : null,
actions: choices.map((c) => CupertinoActionSheetAction(
onPressed: () {
Navigator.of(context).pop(c);
},
child: Text(labelBuilder(c)),
)).toList(),
cancelButton: CupertinoActionSheetAction(
child: const Text('Anulare'),
onPressed: () {
Navigator.of(context).pop();
},
),
);
},
);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
previousPageTitle: 'Info Tren',
middle: Text(pageTitle),
),
child: Builder(
builder: (context) {
final mq = MediaQuery.of(context);
return SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: mq.padding.top,
),
Consumer(
builder: (context, ref, _) {
final currentUiDesign = ref.watch(uiDesignProvider);
return CupertinoListTile(
title: Text(appearanceTitle),
trailing: Text(currentUiDesign.userInterfaceName),
onTap: () async {
final choice = await singleChoice(
context: context,
choices: UiDesign.values,
labelBuilder: (UiDesign ud) => ud.userInterfaceName,
title: appearanceTitle,
);
if (choice != null) {
ref.read(uiDesignProvider.notifier).set(choice);
}
},
);
},
),
Consumer(
builder: (context, ref, _) {
final currentTZ = ref.watch(uiTimeZoneProvider);
return CupertinoListTile(
title: Text(timeZoneTitle),
trailing: Text(currentTZ.type.userInterfaceName),
onTap: () async {
final choice = await singleChoice(
context: context,
choices: UiTimeZoneType.values.where((tz) => tz != UiTimeZoneType.iana).toList(),
labelBuilder: (UiTimeZoneType utzt) => utzt.userInterfaceName,
title: timeZoneTitle,
);
if (choice != null) {
ref.read(uiTimeZoneProvider.notifier).set(UiTimeZone.fromSerString('${choice.name}\n'));
}
},
);
},
),
],
),
);
},
),
);
}
}

65
lib/pages/settings/settings_page_fluent.dart

@ -0,0 +1,65 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/models.dart';
import 'package:info_tren/pages/settings/setings_page.dart';
import 'package:info_tren/providers.dart';
class SettingsPageFluent extends SettingsPageShared {
const SettingsPageFluent({super.key,});
@override
Widget build(BuildContext context) {
return NavigationView(
appBar: NavigationAppBar(
title: Text(pageTitle),
),
content: SingleChildScrollView(
child: Column(
children: [
Consumer(
builder: (context, ref, _) {
final currentUiDesign = ref.watch(uiDesignProvider);
return ListTile(
title: Text(appearanceTitle),
trailing: ComboBox<UiDesign>(
items: UiDesign.values.map((d) => ComboBoxItem(
value: d,
child: Text(d.userInterfaceName),
)).toList(),
value: currentUiDesign,
onChanged: (newUiDesign) {
ref.read(uiDesignProvider.notifier).set(newUiDesign);
},
),
);
},
),
Consumer(
builder: (context, ref, _) {
final currentTZ = ref.watch(uiTimeZoneProvider);
return ListTile(
title: Text(timeZoneTitle),
trailing: ComboBox<UiTimeZoneType>(
items: UiTimeZoneType.values.where((tz) => tz != UiTimeZoneType.iana).map((tzt) => ComboBoxItem(
value: tzt,
child: Text(tzt.userInterfaceName),
)).toList(),
value: currentTZ.type,
onChanged: (newTZ) {
if (newTZ != null) {
ref.read(uiTimeZoneProvider.notifier).set(UiTimeZone.fromSerString('${newTZ.name}\n'));
}
else {
ref.read(uiTimeZoneProvider.notifier).set(null);
}
},
),
);
},
),
],
),
),
);
}
}

66
lib/pages/settings/settings_page_material.dart

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/models.dart';
import 'package:info_tren/pages/settings/setings_page.dart';
import 'package:info_tren/providers.dart';
class SettingsPageMaterial extends SettingsPageShared {
const SettingsPageMaterial({super.key,});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(pageTitle),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
Consumer(
builder: (context, ref, _) {
final currentUiDesign = ref.watch(uiDesignProvider);
return ListTile(
title: Text(appearanceTitle),
trailing: DropdownButton<UiDesign>(
items: UiDesign.values.map((d) => DropdownMenuItem(
value: d,
child: Text(d.userInterfaceName),
)).toList(),
value: currentUiDesign,
onChanged: (newUiDesign) {
ref.read(uiDesignProvider.notifier).set(newUiDesign);
},
),
);
},
),
Consumer(
builder: (context, ref, _) {
final currentTZ = ref.watch(uiTimeZoneProvider);
return ListTile(
title: Text(timeZoneTitle),
trailing: DropdownButton<UiTimeZoneType>(
items: UiTimeZoneType.values.where((tz) => tz != UiTimeZoneType.iana).map((tzt) => DropdownMenuItem(
value: tzt,
child: Text(tzt.userInterfaceName),
)).toList(),
value: currentTZ.type,
onChanged: (newTZ) {
if (newTZ != null) {
ref.read(uiTimeZoneProvider.notifier).set(UiTimeZone.fromSerString('${newTZ.name}\n'));
}
else {
ref.read(uiTimeZoneProvider.notifier).set(null);
}
},
),
);
},
),
],
),
),
);
}
}

3
lib/pages/station_arrdep_page/view_station/view_station.dart

@ -43,8 +43,9 @@ class ViewStationPage extends HookConsumerWidget {
class ViewStationArguments { class ViewStationArguments {
final String stationName; final String stationName;
final DateTime? date;
const ViewStationArguments({required this.stationName}); const ViewStationArguments({required this.stationName, this.date});
} }
abstract class ViewStationPageShared extends StatelessWidget { abstract class ViewStationPageShared extends StatelessWidget {

43
lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart

@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/loading/loading.dart';
import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/components/refresh_future_builder.dart';
import 'package:info_tren/components/sliver_persistent_header_padding.dart'; import 'package:info_tren/components/sliver_persistent_header_padding.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -66,18 +67,7 @@ class ViewStationPageCupertino extends ViewStationPageShared {
onTap: () => onTrainTapped(context, item.train), onTap: () => onTrainTapped(context, item.train),
child: CupertinoFormRow( child: CupertinoFormRow(
prefix: Text.rich( prefix: Text.rich(
TextSpan( trainIdSpan(rank: item.train.rank, number: item.train.number),
children: [
TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? const Color.fromARGB(255, 255, 0, 0) : null,
),
),
const TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
), ),
helper: Text.rich( helper: Text.rich(
TextSpan( TextSpan(
@ -88,7 +78,13 @@ class ViewStationPageCupertino extends ViewStationPageShared {
], ],
), ),
), ),
child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'), child: Consumer(
builder: (context, ref, _) {
final tz = ref.watch(uiTimeZoneProvider);
final time = tz.convertDateTime(item.time);
return Text('${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}');
},
),
), ),
); );
} }
@ -99,18 +95,7 @@ class ViewStationPageCupertino extends ViewStationPageShared {
onTap: () => onTrainTapped(context, item.train), onTap: () => onTrainTapped(context, item.train),
child: CupertinoFormRow( child: CupertinoFormRow(
prefix: Text.rich( prefix: Text.rich(
TextSpan( trainIdSpan(rank: item.train.rank, number: item.train.number),
children: [
TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? const Color.fromARGB(255, 255, 0, 0) : null,
),
),
const TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
), ),
helper: Text.rich( helper: Text.rich(
TextSpan( TextSpan(
@ -121,7 +106,13 @@ class ViewStationPageCupertino extends ViewStationPageShared {
], ],
), ),
), ),
child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'), child: Consumer(
builder: (context, ref, _) {
final tz = ref.watch(uiTimeZoneProvider);
final time = tz.convertDateTime(item.time);
return Text('${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}');
},
),
), ),
); );
} }

49
lib/pages/station_arrdep_page/view_station/view_station_fluent.dart

@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/badge/badge.dart'; import 'package:info_tren/components/badge/badge.dart';
import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/loading/loading.dart';
import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/components/refresh_future_builder.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -141,24 +142,31 @@ class ViewStationPageFluent extends ViewStationPageShared {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( Consumer(
'${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}', builder: (context, ref, _) {
style: TextStyle( final tz = ref.watch(uiTimeZoneProvider);
inherit: true, final time = tz.convertDateTime(item.time);
fontFeatures: const [ return Text(
FontFeature.tabularFigures(), '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
], style: TextStyle(
decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null, inherit: true,
fontSize: item.status.delay != 0 ? 12 : null, fontFeatures: const [
), FontFeature.tabularFigures(),
],
decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null,
fontSize: item.status.delay != 0 ? 12 : null,
),
);
},
), ),
if (item.status.delay != 0) Builder( if (item.status.delay != 0) Consumer(
builder: (context) { builder: (context, ref, _) {
final newTime = item.time.add(Duration(minutes: item.status.delay)); final tz = ref.watch(uiTimeZoneProvider);
final newTime = tz.convertDateTime(item.time.add(Duration(minutes: item.status.delay)));
final delay = item.status.delay > 0; final delay = item.status.delay > 0;
return Text( return Text(
'${newTime.toLocal().hour.toString().padLeft(2, '0')}:${newTime.toLocal().minute.toString().padLeft(2, '0')}', '${newTime.hour.toString().padLeft(2, '0')}:${newTime.minute.toString().padLeft(2, '0')}',
style: TextStyle( style: TextStyle(
inherit: true, inherit: true,
fontFeatures: const [ fontFeatures: const [
@ -177,18 +185,7 @@ class ViewStationPageFluent extends ViewStationPageShared {
child: ListTile( child: ListTile(
// isThreeLine: item.status.delay != 0, // isThreeLine: item.status.delay != 0,
title: Text.rich( title: Text.rich(
TextSpan( trainIdSpan(rank: item.train.rank, number: item.train.number),
children: [
TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? const Color.fromARGB(255, 255, 0, 0) : null,
),
),
const TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
), ),
subtitle: Text.rich( subtitle: Text.rich(
TextSpan( TextSpan(

51
lib/pages/station_arrdep_page/view_station/view_station_material.dart

@ -1,10 +1,11 @@
import 'dart:math'; import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' hide Badge;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/badge/badge.dart'; import 'package:info_tren/components/badge/badge.dart';
import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/loading/loading.dart';
import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/components/refresh_future_builder.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
@ -106,24 +107,31 @@ class ViewStationPageMaterial extends ViewStationPageShared {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( Consumer(
'${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}', builder: (context, ref, _) {
style: TextStyle( final tz = ref.watch(uiTimeZoneProvider);
inherit: true, final time = tz.convertDateTime(item.time);
fontFeatures: const [ return Text(
FontFeature.tabularFigures(), '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
], style: TextStyle(
decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null, inherit: true,
fontSize: item.status.delay != 0 ? 12 : null, fontFeatures: const [
), FontFeature.tabularFigures(),
],
decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null,
fontSize: item.status.delay != 0 ? 12 : null,
),
);
},
), ),
if (item.status.delay != 0) Builder( if (item.status.delay != 0) Consumer(
builder: (context) { builder: (context, ref, _) {
final newTime = item.time.add(Duration(minutes: item.status.delay)); final tz = ref.watch(uiTimeZoneProvider);
final newTime = tz.convertDateTime(item.time.add(Duration(minutes: item.status.delay)));
final delay = item.status.delay > 0; final delay = item.status.delay > 0;
return Text( return Text(
'${newTime.toLocal().hour.toString().padLeft(2, '0')}:${newTime.toLocal().minute.toString().padLeft(2, '0')}', '${newTime.hour.toString().padLeft(2, '0')}:${newTime.minute.toString().padLeft(2, '0')}',
style: TextStyle( style: TextStyle(
inherit: true, inherit: true,
fontFeatures: const [ fontFeatures: const [
@ -142,18 +150,7 @@ class ViewStationPageMaterial extends ViewStationPageShared {
child: ListTile( child: ListTile(
isThreeLine: item.status.delay != 0, isThreeLine: item.status.delay != 0,
title: Text.rich( title: Text.rich(
TextSpan( trainIdSpan(rank: item.train.rank, number: item.train.number),
children: [
TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? const Color.fromARGB(255, 255, 0, 0) : null,
),
),
const TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
), ),
subtitle: Text.rich( subtitle: Text.rich(
TextSpan( TextSpan(

111
lib/pages/train_info_page/view_train/train_info.dart

@ -1,4 +1,7 @@
import 'dart:async';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/api/train_data.dart'; import 'package:info_tren/api/train_data.dart';
import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/loading/loading.dart';
@ -9,59 +12,93 @@ import 'package:info_tren/pages/train_info_page/view_train/train_info_fluent.dar
import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/providers.dart';
class TrainInfo extends HookConsumerWidget {
class TrainInfo extends ConsumerWidget {
static String routeName = "/trainInfo/display"; static String routeName = "/trainInfo/display";
const TrainInfo({super.key,}); const TrainInfo({
super.key,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider); final uiDesign = ref.watch(uiDesignProvider);
final args = ref.watch(trainInfoArgumentsProvider); final args = ref.watch(trainInfoArgumentsProvider);
final trainNumber = args.trainNumber; final trainNumber = args.trainNumber;
final date = args.date; final viewYesterday = useState(false);
final date = args.date ??
return RefreshFutureBuilder<TrainData>( DateTime.now().copyWith(
futureCreator: () => getTrain(trainNumber, date: date), hour: 12,
builder: (context, refresh, replaceFutureBuilder, snapshot) { minute: 0,
void onViewYesterdayTrain() { second: 0,
replaceFutureBuilder(() => getTrain(trainNumber, date: DateTime.now().subtract(const Duration(days: 1)))); millisecond: 0,
} microsecond: 0,
);
final requestDate =
viewYesterday.value ? date.subtract(const Duration(days: 1)) : date;
final trainDataAsync = ref
.watch(trainInfoProvider(trainNumber: trainNumber, date: requestDate));
Future refresh() async {
ref.invalidate(
trainInfoProvider(trainNumber: trainNumber, date: requestDate),
);
await Future.delayed(const Duration(seconds: 1));
}
if ([RefreshFutureBuilderState.none, RefreshFutureBuilderState.waiting].contains(snapshot.state)) { void onViewYesterdayTrain() {
return TrainInfoLoading(title: trainNumber.toString(), loadingText: "Se încarcă...",); viewYesterday.value = !viewYesterday.value;
} }
else if (snapshot.state == RefreshFutureBuilderState.error) {
return TrainInfoError(title: '$trainNumber - Error', error: snapshot.error!, refresh: refresh,);
}
useEffect(() {
final handle = Timer.periodic(const Duration(minutes: 1), (timer) {
refresh();
});
return () {
handle.cancel();
};
});
return trainDataAsync.when(
data: (data) {
switch (uiDesign) { switch (uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
return TrainInfoMaterial( return TrainInfoMaterial(
trainData: snapshot.data!, trainData: data,
refresh: refresh, refresh: refresh,
isRefreshing: snapshot.state == RefreshFutureBuilderState.refreshing, isRefreshing: trainDataAsync.isRefreshing,
onViewYesterdayTrain: onViewYesterdayTrain, onViewYesterdayTrain: onViewYesterdayTrain,
); );
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return TrainInfoCupertino( return TrainInfoCupertino(
trainData: snapshot.data!, trainData: data,
refresh: refresh, refresh: refresh,
isRefreshing: snapshot.state == RefreshFutureBuilderState.refreshing, isRefreshing: trainDataAsync.isRefreshing,
onViewYesterdayTrain: onViewYesterdayTrain, onViewYesterdayTrain: onViewYesterdayTrain,
); );
case UiDesign.FLUENT: case UiDesign.FLUENT:
return TrainInfoFluent( return TrainInfoFluent(
trainData: snapshot.data!, trainData: data,
refresh: refresh, refresh: refresh,
isRefreshing: snapshot.state == RefreshFutureBuilderState.refreshing, isRefreshing: trainDataAsync.isRefreshing,
onViewYesterdayTrain: onViewYesterdayTrain, onViewYesterdayTrain: onViewYesterdayTrain,
); );
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
}, },
error: (e, st) {
return TrainInfoError(
title: '$trainNumber - Error',
error: e,
refresh: refresh,
);
},
loading: () {
return TrainInfoLoading(
title: trainNumber.toString(),
loadingText: "Se încarcă...",
);
},
); );
} }
} }
@ -129,10 +166,12 @@ abstract class TrainInfoLoadingShared extends StatelessWidget {
final Widget loadingWidget; final Widget loadingWidget;
TrainInfoLoadingShared({ TrainInfoLoadingShared({
required this.title, required this.title,
String? loadingText, String? loadingText,
super.key, super.key,
}) : loadingWidget = Loading(text: loadingText,); }) : loadingWidget = Loading(
text: loadingText,
);
} }
class TrainInfoError extends ConsumerWidget { class TrainInfoError extends ConsumerWidget {
@ -181,7 +220,12 @@ abstract class TrainInfoErrorShared extends StatelessWidget {
final Object error; final Object error;
final Future Function()? refresh; final Future Function()? refresh;
const TrainInfoErrorShared({required this.title, required this.error, this.refresh, super.key,}); const TrainInfoErrorShared({
required this.title,
required this.error,
this.refresh,
super.key,
});
} }
class TrainInfoBody extends ConsumerWidget { class TrainInfoBody extends ConsumerWidget {
@ -246,10 +290,15 @@ abstract class TrainInfoBodyShared extends StatelessWidget {
} }
abstract class DisplayTrainYesterdayWarningCommon extends StatelessWidget { abstract class DisplayTrainYesterdayWarningCommon extends StatelessWidget {
static const trainDidNotDepart = 'Acest tren nu a plecat încă din prima gară.'; static const trainDidNotDepart =
static const seeYesterdayTrain = 'Apasă aici pentru a vedea trenul care a plecat ieri.'; 'Acest tren nu a plecat încă din prima gară.';
static const seeYesterdayTrain =
'Apasă aici pentru a vedea trenul care a plecat ieri.';
final void Function() onViewYesterdayTrain; final void Function() onViewYesterdayTrain;
const DisplayTrainYesterdayWarningCommon(this.onViewYesterdayTrain, {super.key,}); const DisplayTrainYesterdayWarningCommon(
this.onViewYesterdayTrain, {
super.key,
});
} }

113
lib/pages/train_info_page/view_train/train_info_cupertino.dart

@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:info_tren/components/cupertino_divider.dart'; import 'package:info_tren/components/cupertino_divider.dart';
import 'package:info_tren/components/sliver_persistent_header_padding.dart'; import 'package:info_tren/components/sliver_persistent_header_padding.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/train_info_page/train_info_constants.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.dart';
@ -214,59 +215,62 @@ class TrainInfoBodyCupertino extends TrainInfoBodyShared {
return Row( return Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Container( SafeArea(
constraints: const BoxConstraints( right: false,
minWidth: 400, child: Container(
maxWidth: 400, constraints: const BoxConstraints(
), minWidth: 400,
child: Column( maxWidth: 400,
mainAxisSize: MainAxisSize.max, ),
children: [ child: Column(
DisplayTrainID(trainData: trainData), mainAxisSize: MainAxisSize.max,
DisplayTrainOperator(trainData: trainData), children: [
DisplayTrainRoute(trainData: trainData), DisplayTrainID(trainData: trainData),
DisplayTrainDeparture(trainData: trainData), DisplayTrainOperator(trainData: trainData),
const CupertinoDivider( DisplayTrainRoute(trainData: trainData),
color: foregroundWhite, DisplayTrainDeparture(trainData: trainData),
), const CupertinoDivider(
DisplayTrainLastInfo(trainData: trainData), color: foregroundWhite,
const CupertinoDivider(), ),
IntrinsicHeight( DisplayTrainLastInfo(trainData: trainData),
child: Row( const CupertinoDivider(),
children: <Widget>[ IntrinsicHeight(
// Expanded( child: Row(
// child: DisplayTrainNextStop(trainData: trainData,), children: <Widget>[
// ), // Expanded(
Expanded( // child: DisplayTrainNextStop(trainData: trainData,),
child: DisplayTrainRouteDuration( // ),
trainData: trainData, Expanded(
child: DisplayTrainRouteDuration(
trainData: trainData,
),
), ),
), // Expanded(
// Expanded( // child: DisplayTrainDestination(trainData: trainData,),
// child: DisplayTrainDestination(trainData: trainData,), // ),
// ), const SizedBox(
const SizedBox( height: double.infinity,
height: double.infinity, child: CupertinoVerticalDivider(),
child: CupertinoVerticalDivider(),
),
Expanded(
child: DisplayTrainRouteDistance(
trainData: trainData,
), ),
), Expanded(
], child: DisplayTrainRouteDistance(
trainData: trainData,
),
),
],
),
), ),
),
const CupertinoDivider(
color: foregroundWhite,
),
if (onViewYesterdayTrain != null && trainData.stations.first.departure!.scheduleTime.compareTo(DateTime.now()) > 0) ...[
DisplayTrainYesterdayWarningCupertino(onViewYesterdayTrain!),
const CupertinoDivider( const CupertinoDivider(
color: foregroundWhite, color: foregroundWhite,
), ),
if (onViewYesterdayTrain != null && trainData.stations.first.departure!.scheduleTime.compareTo(DateTime.now()) > 0) ...[
DisplayTrainYesterdayWarningCupertino(onViewYesterdayTrain!),
const CupertinoDivider(
color: foregroundWhite,
),
],
], ],
], ),
), ),
), ),
Expanded( Expanded(
@ -559,18 +563,7 @@ class DisplayTrainID extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text.rich( child: Text.rich(
TextSpan( trainIdSpan(rank: trainData.rank, number: trainData.number),
children: [
TextSpan(
text: trainData.rank,
style: TextStyle(
color: trainData.rank.startsWith('IR') ? const Color.fromARGB(255, 255, 0, 0) : null,
),
),
const TextSpan(text: ' '),
TextSpan(text: trainData.number,),
],
),
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle, style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
), ),
), ),
@ -1058,8 +1051,8 @@ class DisplayTrainYesterdayWarningCupertino extends DisplayTrainYesterdayWarning
const TextSpan(text: '\n'), const TextSpan(text: '\n'),
TextSpan( TextSpan(
text: DisplayTrainYesterdayWarningCommon.seeYesterdayTrain, text: DisplayTrainYesterdayWarningCommon.seeYesterdayTrain,
style: const TextStyle( style: TextStyle(
color: CupertinoColors.link, color: CupertinoTheme.of(context).primaryColor,
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = onViewYesterdayTrain, ..onTap = onViewYesterdayTrain,

332
lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart

@ -1,11 +1,18 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/badge/badge.dart'; import 'package:info_tren/components/badge/badge.dart';
import 'package:info_tren/components/cupertino_divider.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/providers.dart';
class DisplayTrainStation extends StatelessWidget { class DisplayTrainStation extends StatelessWidget {
final Station station; final TrainDataStation station;
const DisplayTrainStation({required this.station, super.key,}); const DisplayTrainStation({
required this.station,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -13,6 +20,26 @@ class DisplayTrainStation extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
if (station.notes.whereType<TrainDataNoteDepartsAs>().isNotEmpty) ...[
Builder(
builder: (context) {
final note =
station.notes.whereType<TrainDataNoteDepartsAs>().first;
return Padding(
padding: const EdgeInsets.all(2.0),
child: Text.rich(
TextSpan(
children: [
const TextSpan(text: 'Trenul pleacă cu numărul '),
trainIdSpan(rank: note.rank, number: note.number),
],
),
),
);
},
),
const CupertinoDivider(),
],
Row( Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
@ -20,42 +47,38 @@ class DisplayTrainStation extends StatelessWidget {
flex: 1, flex: 1,
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Builder( child: Builder(builder: (context) {
builder: (context) { final departureStatus = station.departure?.status;
final departureStatus = station.departure?.status; final arrivalStatus = station.arrival?.status;
final arrivalStatus = station.arrival?.status; int delay;
int delay; bool real;
bool real; if (departureStatus == null) {
if (departureStatus == null) { delay = arrivalStatus?.delay ?? 0;
delay = arrivalStatus?.delay ?? 0; real = arrivalStatus?.real ?? false;
real = arrivalStatus?.real ?? false; } else if (arrivalStatus == null) {
} delay = departureStatus.delay;
else if (arrivalStatus == null) { real = departureStatus.real;
delay = departureStatus.delay; } else {
real = departureStatus.real; delay = departureStatus.delay;
real = departureStatus.real;
if (!real && arrivalStatus.real) {
delay = arrivalStatus.delay;
real = arrivalStatus.real;
} }
else {
delay = departureStatus.delay;
real = departureStatus.real;
if (!real && arrivalStatus.real) {
delay = arrivalStatus.delay;
real = arrivalStatus.real;
}
}
final isDelayed = delay > 0 && real == true;
final isOnTime = delay <= 0 && real == true;
const isNotScheduled = false;
return Badge(
text: station.km.toString(),
caption: 'km',
isNotScheduled: isNotScheduled,
isDelayed: isDelayed,
isOnTime: isOnTime,
);
} }
),
final isDelayed = delay > 0 && real == true;
final isOnTime = delay <= 0 && real == true;
const isNotScheduled = false;
return Badge(
text: station.km.toString(),
caption: 'km',
isNotScheduled: isNotScheduled,
isDelayed: isDelayed,
isOnTime: isOnTime,
);
}),
), ),
), ),
Title( Title(
@ -65,7 +88,9 @@ class DisplayTrainStation extends StatelessWidget {
flex: 1, flex: 1,
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: station.platform == null ? Container() : Badge(text: station.platform!, caption: 'linia'), child: station.platform == null
? Container()
: Badge(text: station.platform!, caption: 'linia'),
), ),
), ),
], ],
@ -73,16 +98,61 @@ class DisplayTrainStation extends StatelessWidget {
Time( Time(
station: station, station: station,
), ),
if (station.notes.whereType<TrainDataNoteDetachingWagons>().isNotEmpty)
Builder(
builder: (context) {
final note =
station.notes.whereType<TrainDataNoteDetachingWagons>().first;
return Text(
'Trenul detașează vagoane către ${note.station}',
textAlign: TextAlign.center,
);
},
),
if (station.notes.whereType<TrainDataNoteReceivingWagons>().isNotEmpty)
Builder(
builder: (context) {
final note =
station.notes.whereType<TrainDataNoteReceivingWagons>().first;
return Text(
'Trenul primește vagoane de la ${note.station}',
textAlign: TextAlign.center,
);
},
),
Delay( Delay(
station: station, station: station,
), ),
if (station.notes
.whereType<TrainDataNoteTrainNumberChange>()
.isNotEmpty) ...[
const CupertinoDivider(),
Builder(
builder: (context) {
final note = station.notes
.whereType<TrainDataNoteTrainNumberChange>()
.first;
return Padding(
padding: const EdgeInsets.all(2.0),
child: Text.rich(
TextSpan(
children: [
const TextSpan(text: 'Trenul își schimbă numărul în '),
trainIdSpan(rank: note.rank, number: note.number),
],
),
),
);
},
),
],
], ],
); );
} }
} }
class Title extends StatelessWidget { class Title extends StatelessWidget {
final Station station; final TrainDataStation station;
const Title({ const Title({
required this.station, required this.station,
@ -94,17 +164,19 @@ class Title extends StatelessWidget {
return Text( return Text(
station.name, station.name,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22, fontSize: 22,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w500 : FontWeight.w300, fontWeight: MediaQuery.of(context).boldText
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, ? FontWeight.w500
), : FontWeight.w300,
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} }
} }
class Time extends StatelessWidget { class Time extends StatelessWidget {
final Station station; final TrainDataStation station;
const Time({ const Time({
required this.station, required this.station,
@ -135,29 +207,43 @@ class Time extends StatelessWidget {
Text( Text(
"", "",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22, fontSize: 22,
), ),
),
Container(
width: 2,
),
ArrivalTime(
station: station,
),
Expanded(
child: Container(),
),
StopTime(
station: station,
),
Expanded(
child: Container(),
),
DepartureTime(
station: station,
),
Container(
width: 2,
), ),
Container(width: 2,),
ArrivalTime(station: station,),
Expanded(child: Container(),),
StopTime(station: station,),
Expanded(child: Container(),),
DepartureTime(station: station,),
Container(width: 2,),
Text( Text(
"", "",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22, fontSize: 22,
), ),
), ),
], ],
); );
} }
} }
class ArrivalTime extends StatelessWidget { class ArrivalTime extends ConsumerWidget {
final Station station; final TrainDataStation station;
final bool finalStation; final bool finalStation;
const ArrivalTime({ const ArrivalTime({
@ -167,7 +253,9 @@ class ArrivalTime extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final tz = ref.watch(uiTimeZoneProvider);
if (finalStation) { if (finalStation) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -175,24 +263,29 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"", "",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22, fontSize: 22,
), ),
),
Container(
width: 2,
), ),
Container(width: 2,),
const Text("sosire la "), const Text("sosire la "),
ArrivalTime(station: station,), ArrivalTime(
Expanded(child: Container(),), station: station,
),
Expanded(
child: Container(),
),
], ],
); );
} } else {
else {
final delay = station.arrival!.status?.delay ?? 0; final delay = station.arrival!.status?.delay ?? 0;
final time = station.arrival!.scheduleTime.toLocal(); final time = tz.convertDateTime(station.arrival!.scheduleTime);
if (delay == 0) { if (delay == 0) {
return Text("${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}"); return Text(
} "${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}");
else if (delay > 0) { } else if (delay > 0) {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -202,19 +295,18 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed, color: CupertinoColors.destructiveRed,
), ),
), ),
], ],
); );
} } else {
else {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -224,14 +316,14 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.systemGreen, color: CupertinoColors.systemGreen,
), ),
), ),
], ],
); );
@ -241,7 +333,7 @@ class ArrivalTime extends StatelessWidget {
} }
class StopTime extends StatelessWidget { class StopTime extends StatelessWidget {
final Station station; final TrainDataStation station;
const StopTime({ const StopTime({
required this.station, required this.station,
@ -271,14 +363,12 @@ class StopTime extends StatelessWidget {
minutes ? '1 minut' : '1 secundă', minutes ? '1 minut' : '1 secundă',
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} } else if (stopsForInt < 20) {
else if (stopsForInt < 20) {
return Text( return Text(
'$stopsForInt ${minutes ? 'minute' : 'seconde'}', '$stopsForInt ${minutes ? 'minute' : 'seconde'}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} } else {
else {
return Text( return Text(
'$stopsForInt de ${minutes ? 'minute' : 'secunde'}', '$stopsForInt de ${minutes ? 'minute' : 'secunde'}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -291,8 +381,8 @@ class StopTime extends StatelessWidget {
} }
} }
class DepartureTime extends StatelessWidget { class DepartureTime extends ConsumerWidget {
final Station station; final TrainDataStation station;
final bool firstStation; final bool firstStation;
const DepartureTime({ const DepartureTime({
@ -302,32 +392,38 @@ class DepartureTime extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final tz = ref.watch(uiTimeZoneProvider);
if (firstStation) { if (firstStation) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Expanded(child: Container(),), Expanded(
child: Container(),
),
const Text("plecare la "), const Text("plecare la "),
DepartureTime(station: station,), DepartureTime(
Container(width: 2,), station: station,
),
Container(
width: 2,
),
Text( Text(
"", "",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 22, fontSize: 22,
), ),
), ),
], ],
); );
} } else {
else {
final delay = station.departure!.status?.delay ?? 0; final delay = station.departure!.status?.delay ?? 0;
final time = station.departure!.scheduleTime.toLocal(); final time = tz.convertDateTime(station.departure!.scheduleTime);
if (delay == 0) { if (delay == 0) {
return Text("${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}"); return Text(
} "${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}");
else if (delay > 0) { } else if (delay > 0) {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -337,19 +433,18 @@ class DepartureTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed, color: CupertinoColors.destructiveRed,
), ),
), ),
], ],
); );
} } else {
else {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -359,14 +454,14 @@ class DepartureTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.systemGreen, color: CupertinoColors.systemGreen,
), ),
), ),
], ],
); );
@ -376,7 +471,7 @@ class DepartureTime extends StatelessWidget {
} }
class Delay extends StatelessWidget { class Delay extends StatelessWidget {
final Station station; final TrainDataStation station;
const Delay({ const Delay({
required this.station, required this.station,
@ -399,20 +494,19 @@ class Delay extends StatelessWidget {
return Text( return Text(
"$delay ${delay == 1 ? 'minut' : 'minute'} întârziere", "$delay ${delay == 1 ? 'minut' : 'minute'} întârziere",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.destructiveRed, color: CupertinoColors.destructiveRed,
fontSize: 14, fontSize: 14,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
); );
} } else if (delay < 0) {
else if (delay < 0) {
return Text( return Text(
"${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme", "${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme",
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
color: CupertinoColors.systemGreen, color: CupertinoColors.systemGreen,
fontSize: 14, fontSize: 14,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
); );
} }

20
lib/pages/train_info_page/view_train/train_info_fluent.dart

@ -1,5 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.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.dart';
@ -303,22 +304,7 @@ class DisplayTrainID extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text.rich( return Text.rich(
TextSpan( trainIdSpan(rank: trainData.rank, number: trainData.number),
children: [
TextSpan(
text: trainData.rank,
style: TextStyle(
color: trainData.rank.startsWith('IR')
? const Color.fromARGB(255, 255, 0, 0)
: null,
),
),
const TextSpan(text: ' '),
TextSpan(
text: trainData.number,
),
],
),
style: FluentTheme.of(context).typography.title?.copyWith( style: FluentTheme.of(context).typography.title?.copyWith(
color: FluentTheme.of(context).typography.body?.color, color: FluentTheme.of(context).typography.body?.color,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -762,7 +748,7 @@ class DisplayTrainYesterdayWarningFluent
TextSpan( TextSpan(
text: DisplayTrainYesterdayWarningCommon.seeYesterdayTrain, text: DisplayTrainYesterdayWarningCommon.seeYesterdayTrain,
style: TextStyle( style: TextStyle(
color: Colors.blue, color: FluentTheme.of(context).accentColor,// Colors.blue,
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = onViewYesterdayTrain, ..onTap = onViewYesterdayTrain,

368
lib/pages/train_info_page/view_train/train_info_fluent_DisplayTrainStation.dart

@ -1,35 +1,62 @@
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/badge/badge.dart'; import 'package:info_tren/components/badge/badge.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/providers.dart';
class DisplayTrainStation extends StatelessWidget { class DisplayTrainStation extends StatelessWidget {
final Station station; final TrainDataStation station;
final void Function()? onTap; final void Function()? onTap;
const DisplayTrainStation({required this.station, this.onTap, super.key,}); const DisplayTrainStation({
required this.station,
this.onTap,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Column(
padding: const EdgeInsets.all(2), mainAxisSize: MainAxisSize.min,
child: HoverButton( children: [
onPressed: onTap, if (station.notes.whereType<TrainDataNoteDepartsAs>().isNotEmpty)
builder: (context, states) { Builder(
return Card( builder: (context) {
padding: const EdgeInsets.all(2), final note =
child: Column( station.notes.whereType<TrainDataNoteDepartsAs>().first;
mainAxisSize: MainAxisSize.min, return Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: const EdgeInsets.all(2.0),
children: <Widget>[ child: Text.rich(
Row( TextSpan(
mainAxisSize: MainAxisSize.max, children: [
const TextSpan(text: 'Trenul pleacă cu numărul '),
trainIdSpan(rank: note.rank, number: note.number),
],
),
),
);
},
),
Padding(
padding: const EdgeInsets.all(2),
child: HoverButton(
onPressed: onTap,
builder: (context, states) {
return Card(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Expanded( Row(
flex: 1, mainAxisSize: MainAxisSize.max,
child: Align( children: <Widget>[
alignment: Alignment.centerLeft, Expanded(
child: Builder( flex: 1,
builder: (context) { child: Align(
alignment: Alignment.centerLeft,
child: Builder(builder: (context) {
final departureStatus = station.departure?.status; final departureStatus = station.departure?.status;
final arrivalStatus = station.arrival?.status; final arrivalStatus = station.arrival?.status;
int delay; int delay;
@ -37,12 +64,10 @@ class DisplayTrainStation extends StatelessWidget {
if (departureStatus == null) { if (departureStatus == null) {
delay = arrivalStatus?.delay ?? 0; delay = arrivalStatus?.delay ?? 0;
real = arrivalStatus?.real ?? false; real = arrivalStatus?.real ?? false;
} } else if (arrivalStatus == null) {
else if (arrivalStatus == null) {
delay = departureStatus.delay; delay = departureStatus.delay;
real = departureStatus.real; real = departureStatus.real;
} } else {
else {
delay = departureStatus.delay; delay = departureStatus.delay;
real = departureStatus.real; real = departureStatus.real;
if (!real && arrivalStatus.real) { if (!real && arrivalStatus.real) {
@ -62,41 +87,94 @@ class DisplayTrainStation extends StatelessWidget {
isDelayed: isDelayed, isDelayed: isDelayed,
isOnTime: isOnTime, isOnTime: isOnTime,
); );
} }),
),
), ),
), Title(
station: station,
),
Expanded(
flex: 1,
child: (station.platform == null)
? Container()
: Align(
alignment: Alignment.centerRight,
child: Badge(
text: station.platform!,
caption: 'linia',
),
),
),
],
), ),
Title( Time(
station: station, station: station,
), ),
Expanded( if (station.notes
flex: 1, .whereType<TrainDataNoteDetachingWagons>()
child: (station.platform == null) .isNotEmpty)
? Container() Builder(
: Align( builder: (context) {
alignment: Alignment.centerRight, final note = station.notes
child: Badge(text: station.platform!, caption: 'linia',), .whereType<TrainDataNoteDetachingWagons>()
.first;
return Text(
'Trenul detașează vagoane către ${note.station}',
textAlign: TextAlign.center,
);
},
),
if (station.notes
.whereType<TrainDataNoteReceivingWagons>()
.isNotEmpty)
Builder(
builder: (context) {
final note = station.notes
.whereType<TrainDataNoteReceivingWagons>()
.first;
return Text(
'Trenul primește vagoane de la ${note.station}',
textAlign: TextAlign.center,
);
},
), ),
Delay(
station: station,
), ),
], ],
), ),
Time( );
station: station, },
), ),
Delay( ),
station: station, if (station.notes
.whereType<TrainDataNoteTrainNumberChange>()
.isNotEmpty)
Builder(
builder: (context) {
final note = station.notes
.whereType<TrainDataNoteTrainNumberChange>()
.first;
return Padding(
padding: const EdgeInsets.all(2.0),
child: Text.rich(
TextSpan(
children: [
const TextSpan(text: 'Trenul își schimbă numărul în '),
trainIdSpan(rank: note.rank, number: note.number),
],
),
), ),
], );
), },
); ),
}, ],
),
); );
} }
} }
class Title extends StatelessWidget { class Title extends StatelessWidget {
final Station station; final TrainDataStation station;
const Title({ const Title({
required this.station, required this.station,
@ -105,20 +183,22 @@ class Title extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text( return Text(
station.name, station.name,
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
fontSize: 22, fontSize: 22,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w500 : FontWeight.w300, fontWeight: MediaQuery.of(context).boldText
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, ? FontWeight.w500
), : FontWeight.w300,
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} }
} }
class Time extends StatelessWidget { class Time extends StatelessWidget {
final Station station; final TrainDataStation station;
const Time({ const Time({
required this.station, required this.station,
@ -149,29 +229,43 @@ class Time extends StatelessWidget {
Text( Text(
"", "",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
fontSize: 22, fontSize: 22,
), ),
),
Container(
width: 2,
),
ArrivalTime(
station: station,
),
Expanded(
child: Container(),
),
StopTime(
station: station,
),
Expanded(
child: Container(),
),
DepartureTime(
station: station,
),
Container(
width: 2,
), ),
Container(width: 2,),
ArrivalTime(station: station,),
Expanded(child: Container(),),
StopTime(station: station,),
Expanded(child: Container(),),
DepartureTime(station: station,),
Container(width: 2,),
Text( Text(
"", "",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
fontSize: 22, fontSize: 22,
), ),
), ),
], ],
); );
} }
} }
class ArrivalTime extends StatelessWidget { class ArrivalTime extends ConsumerWidget {
final Station station; final TrainDataStation station;
final bool finalStation; final bool finalStation;
const ArrivalTime({ const ArrivalTime({
@ -181,7 +275,8 @@ class ArrivalTime extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final tz = ref.watch(uiTimeZoneProvider);
if (station.arrival == null) { if (station.arrival == null) {
return Container(); return Container();
} }
@ -192,24 +287,29 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"", "",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
fontSize: 22, fontSize: 22,
), ),
),
Container(
width: 2,
), ),
Container(width: 2,),
const Text("sosire la "), const Text("sosire la "),
ArrivalTime(station: station,), ArrivalTime(
Expanded(child: Container(),), station: station,
),
Expanded(
child: Container(),
),
], ],
); );
} } else {
else {
final delay = station.arrival!.status?.delay ?? 0; final delay = station.arrival!.status?.delay ?? 0;
final time = station.arrival!.scheduleTime.toLocal(); final time = tz.convertDateTime(station.arrival!.scheduleTime);
if (delay == 0) { if (delay == 0) {
return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); return Text(
} "${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}");
else if (delay > 0) { } else if (delay > 0) {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -219,20 +319,19 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
// color: Colors.red.shade300, // color: Colors.red.shade300,
color: Colors.red.lighter, color: Colors.red.lighter,
), ),
), ),
], ],
); );
} } else {
else {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -242,15 +341,15 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
// color: Colors.green.shade300, // color: Colors.green.shade300,
color: Colors.green.lighter, color: Colors.green.lighter,
), ),
), ),
], ],
); );
@ -260,7 +359,7 @@ class ArrivalTime extends StatelessWidget {
} }
class StopTime extends StatelessWidget { class StopTime extends StatelessWidget {
final Station station; final TrainDataStation station;
const StopTime({ const StopTime({
required this.station, required this.station,
@ -289,14 +388,12 @@ class StopTime extends StatelessWidget {
"1 ${minutes ? 'minut' : 'secundă'}", "1 ${minutes ? 'minut' : 'secundă'}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} } else if (stopsForInt < 20) {
else if (stopsForInt < 20) {
return Text( return Text(
"$stopsForInt ${minutes ? 'minute' : 'secunde'}", "$stopsForInt ${minutes ? 'minute' : 'secunde'}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} } else {
else {
return Text( return Text(
"$stopsForInt de ${minutes ? 'minute' : 'secunde'}", "$stopsForInt de ${minutes ? 'minute' : 'secunde'}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -309,8 +406,8 @@ class StopTime extends StatelessWidget {
} }
} }
class DepartureTime extends StatelessWidget { class DepartureTime extends ConsumerWidget {
final Station station; final TrainDataStation station;
final bool firstStation; final bool firstStation;
const DepartureTime({ const DepartureTime({
@ -320,7 +417,8 @@ class DepartureTime extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final tz = ref.watch(uiTimeZoneProvider);
if (station.departure == null) { if (station.departure == null) {
return Container(); return Container();
} }
@ -328,27 +426,32 @@ class DepartureTime extends StatelessWidget {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Expanded(child: Container(),), Expanded(
child: Container(),
),
const Text("plecare la "), const Text("plecare la "),
DepartureTime(station: station,), DepartureTime(
Container(width: 2,), station: station,
),
Container(
width: 2,
),
Text( Text(
"", "",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
fontSize: 22, fontSize: 22,
), ),
), ),
], ],
); );
} } else {
else {
final delay = station.departure!.status?.delay ?? 0; final delay = station.departure!.status?.delay ?? 0;
final time = station.departure!.scheduleTime.toLocal(); final time = tz.convertDateTime(station.departure!.scheduleTime);
if (delay == 0) { if (delay == 0) {
return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); return Text(
} "${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}");
else if (delay > 0) { } else if (delay > 0) {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -358,20 +461,19 @@ class DepartureTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
// color: Colors.red.shade300, // color: Colors.red.shade300,
color: Colors.red.lighter, color: Colors.red.lighter,
), ),
), ),
], ],
); );
} } else {
else {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -381,15 +483,15 @@ class DepartureTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
// color: Colors.green.shade300, // color: Colors.green.shade300,
color: Colors.green.lighter, color: Colors.green.lighter,
), ),
), ),
], ],
); );
@ -398,9 +500,8 @@ class DepartureTime extends StatelessWidget {
} }
} }
class Delay extends StatelessWidget { class Delay extends StatelessWidget {
final Station station; final TrainDataStation station;
const Delay({ const Delay({
required this.station, required this.station,
@ -423,22 +524,21 @@ class Delay extends StatelessWidget {
return Text( return Text(
"$delay ${delay == 1 ? 'minut' : 'minute'} întârziere", "$delay ${delay == 1 ? 'minut' : 'minute'} întârziere",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
// color: Colors.red.shade300, // color: Colors.red.shade300,
color: Colors.red.lighter, color: Colors.red.lighter,
fontSize: 14, fontSize: 14,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
); );
} } else if (delay < 0) {
else if (delay < 0) {
return Text( return Text(
"${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme", "${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme",
style: FluentTheme.of(context).typography.body?.copyWith( style: FluentTheme.of(context).typography.body?.copyWith(
// color: Colors.green.shade300, // color: Colors.green.shade300,
color: Colors.green.lighter, color: Colors.green.lighter,
fontSize: 14, fontSize: 14,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
); );
} }

143
lib/pages/train_info_page/view_train/train_info_material.dart

@ -1,6 +1,7 @@
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:info_tren/components/slim_app_bar.dart'; import 'package:info_tren/components/slim_app_bar.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.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.dart';
@ -8,7 +9,11 @@ import 'package:info_tren/pages/train_info_page/view_train/train_info_material_D
import 'package:info_tren/utils/state_to_string.dart'; import 'package:info_tren/utils/state_to_string.dart';
class TrainInfoLoadingMaterial extends TrainInfoLoadingShared { class TrainInfoLoadingMaterial extends TrainInfoLoadingShared {
TrainInfoLoadingMaterial({required super.title, super.loadingText, super.key,}); TrainInfoLoadingMaterial({
required super.title,
super.loadingText,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -76,13 +81,34 @@ class TrainInfoMaterial extends TrainInfoShared {
builder: (context) { builder: (context) {
return Scaffold( return Scaffold(
appBar: isSmallScreen(context) appBar: isSmallScreen(context)
? null ? null
: AppBar( : AppBar(
centerTitle: true, centerTitle: true,
title: Text( title: Text(
"Informații despre ${trainData.rank} ${trainData.number}", 'Informații despre ${trainData.rank} ${trainData.number}',
),
actions: [
IconButton(
tooltip: 'Reîncarcă',
icon: (isRefreshing ?? false)
? const Center(
child: SizedBox(
height: 16,
width: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
)
: const Icon(Icons.refresh),
onPressed: (isRefreshing ?? false)
? null
: () {
refresh?.call();
},
),
],
), ),
),
body: Column( body: Column(
children: <Widget>[ children: <Widget>[
if (isSmallScreen(context)) if (isSmallScreen(context))
@ -171,16 +197,15 @@ class TrainInfoBodyMaterial extends TrainInfoBodyShared {
if (onViewYesterdayTrain != null && if (onViewYesterdayTrain != null &&
trainData.stations.first.departure!.scheduleTime trainData.stations.first.departure!.scheduleTime
.compareTo(DateTime.now()) > .compareTo(DateTime.now()) >
0) 0) ...[
...[ DisplayTrainYesterdayWarningMaterial(
DisplayTrainYesterdayWarningMaterial( onViewYesterdayTrain!,
onViewYesterdayTrain!, ),
), Divider(
Divider( color: Colors.white70,
color: Colors.white70, height: isSmallScreen(context) ? 8 : 16,
height: isSmallScreen(context) ? 8 : 16, ),
), ],
],
], ],
), ),
), ),
@ -192,10 +217,7 @@ class TrainInfoBodyMaterial extends TrainInfoBodyShared {
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Container( child: Container(
height: MediaQuery height: MediaQuery.of(context).viewPadding.bottom,
.of(context)
.viewPadding
.bottom,
), ),
), ),
], ],
@ -203,8 +225,7 @@ class TrainInfoBodyMaterial extends TrainInfoBodyShared {
), ),
], ],
); );
} } else {
else {
return CustomScrollView( return CustomScrollView(
slivers: <Widget>[ slivers: <Widget>[
SliverToBoxAdapter( SliverToBoxAdapter(
@ -280,11 +301,11 @@ class TrainInfoBodyMaterial extends TrainInfoBodyShared {
), ),
if (onViewYesterdayTrain != null && if (onViewYesterdayTrain != null &&
trainData.stations.first.departure!.scheduleTime trainData.stations.first.departure!.scheduleTime
.compareTo(DateTime.now()) > .compareTo(DateTime.now()) >
0) ...[ 0) ...[
SliverToBoxAdapter( SliverToBoxAdapter(
child: DisplayTrainYesterdayWarningMaterial( child:
onViewYesterdayTrain!), DisplayTrainYesterdayWarningMaterial(onViewYesterdayTrain!),
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Divider( child: Divider(
@ -298,10 +319,7 @@ class TrainInfoBodyMaterial extends TrainInfoBodyShared {
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Container( child: Container(
height: MediaQuery height: MediaQuery.of(context).viewPadding.bottom,
.of(context)
.viewPadding
.bottom,
), ),
), ),
], ],
@ -313,27 +331,15 @@ class TrainInfoBodyMaterial extends TrainInfoBodyShared {
class DisplayTrainID extends StatelessWidget { class DisplayTrainID extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainID({required this.trainData, super.key,}); const DisplayTrainID({
required this.trainData,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text.rich( return Text.rich(
TextSpan( trainIdSpan(rank: trainData.rank, number: trainData.number),
children: [
TextSpan(
text: trainData.rank,
style: TextStyle(
color: trainData.rank.startsWith('IR')
? const Color.fromARGB(255, 255, 0, 0)
: null,
),
),
const TextSpan(text: ' '),
TextSpan(
text: trainData.number,
),
],
),
style: (isSmallScreen(context) style: (isSmallScreen(context)
? Theme.of(context).textTheme.headlineMedium ? Theme.of(context).textTheme.headlineMedium
: Theme.of(context).textTheme.displaySmall) : Theme.of(context).textTheme.displaySmall)
@ -349,7 +355,10 @@ class DisplayTrainID extends StatelessWidget {
class DisplayTrainOperator extends StatelessWidget { class DisplayTrainOperator extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainOperator({required this.trainData, super.key,}); const DisplayTrainOperator({
required this.trainData,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -367,7 +376,10 @@ class DisplayTrainOperator extends StatelessWidget {
class DisplayTrainRoute extends StatelessWidget { class DisplayTrainRoute extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainRoute({required this.trainData, super.key,}); const DisplayTrainRoute({
required this.trainData,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -432,7 +444,10 @@ class DisplayTrainDeparture extends StatelessWidget {
class DisplayTrainLastInfo extends StatelessWidget { class DisplayTrainLastInfo extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainLastInfo({required this.trainData, super.key,}); const DisplayTrainLastInfo({
required this.trainData,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -624,7 +639,10 @@ class DisplayTrainLastInfo extends StatelessWidget {
class DisplayTrainDestination extends StatelessWidget { class DisplayTrainDestination extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainDestination({required this.trainData, super.key,}); const DisplayTrainDestination({
required this.trainData,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -728,7 +746,10 @@ class DisplayTrainDestination extends StatelessWidget {
class DisplayTrainRouteDistance extends StatelessWidget { class DisplayTrainRouteDistance extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainRouteDistance({required this.trainData, super.key,}); const DisplayTrainRouteDistance({
required this.trainData,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -765,7 +786,10 @@ class DisplayTrainRouteDistance extends StatelessWidget {
class DisplayTrainRouteDuration extends StatelessWidget { class DisplayTrainRouteDuration extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainRouteDuration({required this.trainData, super.key,}); const DisplayTrainRouteDuration({
required this.trainData,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -848,7 +872,10 @@ class DisplayTrainRouteDuration extends StatelessWidget {
class DisplayTrainYesterdayWarningMaterial class DisplayTrainYesterdayWarningMaterial
extends DisplayTrainYesterdayWarningCommon { extends DisplayTrainYesterdayWarningCommon {
const DisplayTrainYesterdayWarningMaterial(super.onViewYesterdayTrain, {super.key,}); const DisplayTrainYesterdayWarningMaterial(
super.onViewYesterdayTrain, {
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -884,7 +911,10 @@ class DisplayTrainYesterdayWarningMaterial
class DisplayTrainStations extends StatelessWidget { class DisplayTrainStations extends StatelessWidget {
final TrainData trainData; final TrainData trainData;
const DisplayTrainStations({required this.trainData, super.key,}); const DisplayTrainStations({
required this.trainData,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -898,7 +928,8 @@ class DisplayTrainStations extends StatelessWidget {
onTap: () { onTap: () {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
ViewStationPage.routeName, ViewStationPage.routeName,
arguments: ViewStationArguments(stationName: trainData.stations[index].name), arguments: ViewStationArguments(
stationName: trainData.stations[index].name),
); );
}, },
), ),

406
lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart

@ -1,100 +1,178 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' hide Badge;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/train_id_text_span.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/components/badge/badge.dart'; import 'package:info_tren/components/badge/badge.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart';
import 'package:info_tren/providers.dart';
class DisplayTrainStation extends StatelessWidget { class DisplayTrainStation extends StatelessWidget {
final Station station; final TrainDataStation station;
final void Function()? onTap; final void Function()? onTap;
const DisplayTrainStation({required this.station, this.onTap, super.key,}); const DisplayTrainStation({
required this.station,
this.onTap,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Column(
child: InkWell( mainAxisSize: MainAxisSize.min,
onTap: onTap, children: [
child: Padding( if (station.notes.whereType<TrainDataNoteDepartsAs>().isNotEmpty)
padding: const EdgeInsets.all(2), Builder(
child: Column( builder: (context) {
mainAxisSize: MainAxisSize.min, final note =
crossAxisAlignment: CrossAxisAlignment.center, station.notes.whereType<TrainDataNoteDepartsAs>().first;
children: <Widget>[ return Padding(
Row( padding: const EdgeInsets.all(2.0),
mainAxisSize: MainAxisSize.max, child: Text.rich(
TextSpan(
children: [
const TextSpan(text: 'Trenul pleacă cu numărul '),
trainIdSpan(rank: note.rank, number: note.number),
],
),
),
);
},
),
Card(
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(2),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Expanded( Row(
flex: 1, mainAxisSize: MainAxisSize.max,
child: Align( children: <Widget>[
alignment: Alignment.centerLeft, Expanded(
child: Builder( flex: 1,
builder: (context) { child: Align(
final departureStatus = station.departure?.status; alignment: Alignment.centerLeft,
final arrivalStatus = station.arrival?.status; child: Builder(builder: (context) {
int delay; final departureStatus = station.departure?.status;
bool real; final arrivalStatus = station.arrival?.status;
if (departureStatus == null) { int delay;
delay = arrivalStatus?.delay ?? 0; bool real;
real = arrivalStatus?.real ?? false; if (departureStatus == null) {
} delay = arrivalStatus?.delay ?? 0;
else if (arrivalStatus == null) { real = arrivalStatus?.real ?? false;
delay = departureStatus.delay; } else if (arrivalStatus == null) {
real = departureStatus.real; delay = departureStatus.delay;
} real = departureStatus.real;
else { } else {
delay = departureStatus.delay; delay = departureStatus.delay;
real = departureStatus.real; real = departureStatus.real;
if (!real && arrivalStatus.real) { if (!real && arrivalStatus.real) {
delay = arrivalStatus.delay; delay = arrivalStatus.delay;
real = arrivalStatus.real; real = arrivalStatus.real;
}
} }
}
final isDelayed = delay > 0 && real == true;
final isDelayed = delay > 0 && real == true; final isOnTime = delay <= 0 && real == true;
final isOnTime = delay <= 0 && real == true; const isNotScheduled = false;
const isNotScheduled = false;
return Badge(
return Badge( text: station.km.toString(),
text: station.km.toString(), caption: 'km',
caption: 'km', isNotScheduled: isNotScheduled,
isNotScheduled: isNotScheduled, isDelayed: isDelayed,
isDelayed: isDelayed, isOnTime: isOnTime,
isOnTime: isOnTime, );
); }),
} ),
), ),
), Title(
station: station,
),
Expanded(
flex: 1,
child: (station.platform == null)
? Container()
: Align(
alignment: Alignment.centerRight,
child: Badge(
text: station.platform!,
caption: 'linia',
),
),
),
],
), ),
Title( Time(
station: station, station: station,
), ),
Expanded( if (station.notes
flex: 1, .whereType<TrainDataNoteDetachingWagons>()
child: (station.platform == null) .isNotEmpty)
? Container() Builder(
: Align( builder: (context) {
alignment: Alignment.centerRight, final note = station.notes
child: Badge(text: station.platform!, caption: 'linia',), .whereType<TrainDataNoteDetachingWagons>()
), .first;
return Text(
'Trenul detașează vagoane către ${note.station}',
textAlign: TextAlign.center,
);
},
),
if (station.notes
.whereType<TrainDataNoteReceivingWagons>()
.isNotEmpty)
Builder(
builder: (context) {
final note = station.notes
.whereType<TrainDataNoteReceivingWagons>()
.first;
return Text(
'Trenul primește vagoane de la ${note.station}',
textAlign: TextAlign.center,
);
},
),
Delay(
station: station,
), ),
], ],
), ),
Time( ),
station: station,
),
Delay(
station: station,
),
],
), ),
), ),
), if (station.notes
.whereType<TrainDataNoteTrainNumberChange>()
.isNotEmpty)
Builder(
builder: (context) {
final note = station.notes
.whereType<TrainDataNoteTrainNumberChange>()
.first;
return Padding(
padding: const EdgeInsets.all(2.0),
child: Text.rich(
TextSpan(
children: [
const TextSpan(text: 'Trenul își schimbă numărul în '),
trainIdSpan(rank: note.rank, number: note.number),
],
),
),
);
},
),
],
); );
} }
} }
class Title extends StatelessWidget { class Title extends StatelessWidget {
final Station station; final TrainDataStation station;
const Title({ const Title({
required this.station, required this.station,
@ -103,20 +181,22 @@ class Title extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text( return Text(
station.name, station.name,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22, fontSize: isSmallScreen(context) ? 18 : 22,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w500 : FontWeight.w300, fontWeight: MediaQuery.of(context).boldText
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, ? FontWeight.w500
), : FontWeight.w300,
// fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} }
} }
class Time extends StatelessWidget { class Time extends StatelessWidget {
final Station station; final TrainDataStation station;
const Time({ const Time({
required this.station, required this.station,
@ -147,29 +227,43 @@ class Time extends StatelessWidget {
Text( Text(
"", "",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22, 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,
), ),
Container(width: 2,),
ArrivalTime(station: station,),
Expanded(child: Container(),),
StopTime(station: station,),
Expanded(child: Container(),),
DepartureTime(station: station,),
Container(width: 2,),
Text( Text(
"", "",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22, fontSize: isSmallScreen(context) ? 18 : 22,
), ),
), ),
], ],
); );
} }
} }
class ArrivalTime extends StatelessWidget { class ArrivalTime extends ConsumerWidget {
final Station station; final TrainDataStation station;
final bool finalStation; final bool finalStation;
const ArrivalTime({ const ArrivalTime({
@ -179,7 +273,8 @@ class ArrivalTime extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final tz = ref.watch(uiTimeZoneProvider);
if (station.arrival == null) { if (station.arrival == null) {
return Container(); return Container();
} }
@ -190,24 +285,29 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"", "",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: isSmallScreen(context) ? 18 : 22, fontSize: isSmallScreen(context) ? 18 : 22,
), ),
),
Container(
width: 2,
), ),
Container(width: 2,),
const Text("sosire la "), const Text("sosire la "),
ArrivalTime(station: station,), ArrivalTime(
Expanded(child: Container(),), station: station,
),
Expanded(
child: Container(),
),
], ],
); );
} } else {
else {
final delay = station.arrival!.status?.delay ?? 0; final delay = station.arrival!.status?.delay ?? 0;
final time = station.arrival!.scheduleTime.toLocal(); final time = tz.convertDateTime(station.arrival!.scheduleTime);
if (delay == 0) { if (delay == 0) {
return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); return Text(
} "${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}");
else if (delay > 0) { } else if (delay > 0) {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -217,19 +317,18 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.red.shade300, color: Colors.red.shade300,
), ),
), ),
], ],
); );
} } else {
else {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -239,14 +338,14 @@ class ArrivalTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.green.shade300, color: Colors.green.shade300,
), ),
), ),
], ],
); );
@ -256,7 +355,7 @@ class ArrivalTime extends StatelessWidget {
} }
class StopTime extends StatelessWidget { class StopTime extends StatelessWidget {
final Station station; final TrainDataStation station;
const StopTime({ const StopTime({
required this.station, required this.station,
@ -285,14 +384,12 @@ class StopTime extends StatelessWidget {
"1 ${minutes ? 'minut' : 'secundă'}", "1 ${minutes ? 'minut' : 'secundă'}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} } else if (stopsForInt < 20) {
else if (stopsForInt < 20) {
return Text( return Text(
"$stopsForInt ${minutes ? 'minute' : 'secunde'}", "$stopsForInt ${minutes ? 'minute' : 'secunde'}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} } else {
else {
return Text( return Text(
"$stopsForInt de ${minutes ? 'minute' : 'secunde'}", "$stopsForInt de ${minutes ? 'minute' : 'secunde'}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -305,8 +402,8 @@ class StopTime extends StatelessWidget {
} }
} }
class DepartureTime extends StatelessWidget { class DepartureTime extends ConsumerWidget {
final Station station; final TrainDataStation station;
final bool firstStation; final bool firstStation;
const DepartureTime({ const DepartureTime({
@ -316,7 +413,8 @@ class DepartureTime extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final tz = ref.watch(uiTimeZoneProvider);
if (station.departure == null) { if (station.departure == null) {
return Container(); return Container();
} }
@ -324,27 +422,32 @@ class DepartureTime extends StatelessWidget {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Expanded(child: Container(),), Expanded(
child: Container(),
),
const Text("plecare la "), const Text("plecare la "),
DepartureTime(station: station,), DepartureTime(
Container(width: 2,), station: station,
),
Container(
width: 2,
),
Text( Text(
"", "",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 22, fontSize: 22,
), ),
), ),
], ],
); );
} } else {
else {
final delay = station.departure!.status?.delay ?? 0; final delay = station.departure!.status?.delay ?? 0;
final time = station.departure!.scheduleTime.toLocal(); final time = tz.convertDateTime(station.departure!.scheduleTime);
if (delay == 0) { if (delay == 0) {
return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); return Text(
} "${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}");
else if (delay > 0) { } else if (delay > 0) {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -354,19 +457,18 @@ class DepartureTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.red.shade300, color: Colors.red.shade300,
), ),
), ),
], ],
); );
} } else {
else {
final oldDate = time; final oldDate = time;
final newDate = oldDate.add(Duration(minutes: delay)); final newDate = oldDate.add(Duration(minutes: delay));
@ -376,14 +478,14 @@ class DepartureTime extends StatelessWidget {
Text( Text(
"${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}", "${oldDate.hour.toString().padLeft(2, '0')}:${oldDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
decoration: TextDecoration.lineThrough, decoration: TextDecoration.lineThrough,
), ),
), ),
Text( Text(
"${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}", "${newDate.hour.toString().padLeft(2, '0')}:${newDate.minute.toString().padLeft(2, '0')}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.green.shade300, color: Colors.green.shade300,
), ),
), ),
], ],
); );
@ -392,9 +494,8 @@ class DepartureTime extends StatelessWidget {
} }
} }
class Delay extends StatelessWidget { class Delay extends StatelessWidget {
final Station station; final TrainDataStation station;
const Delay({ const Delay({
required this.station, required this.station,
@ -417,20 +518,19 @@ class Delay extends StatelessWidget {
return Text( return Text(
"$delay ${delay == 1 ? 'minut' : 'minute'} întârziere", "$delay ${delay == 1 ? 'minut' : 'minute'} întârziere",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.red.shade300, color: Colors.red.shade300,
fontSize: 14, fontSize: 14,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
); );
} } else if (delay < 0) {
else if (delay < 0) {
return Text( return Text(
"${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme", "${-delay} ${delay == -1 ? 'minut' : 'minute'} mai devreme",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.green.shade300, color: Colors.green.shade300,
fontSize: 14, fontSize: 14,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
); );
} }

81
lib/providers.dart

@ -1,41 +1,87 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/api/station_data.dart'; import 'package:info_tren/api/station_data.dart';
import 'package:info_tren/api/train_data.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.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.dart';
import 'package:info_tren/utils/default_ui_design.dart'; import 'package:info_tren/utils/default_ui_design.dart';
import 'package:info_tren/utils/iterable_extensions.dart'; import 'package:info_tren/utils/iterable_extensions.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
part 'providers.g.dart';
final sharedPreferencesProvider = Provider<SharedPreferences>( final sharedPreferencesProvider = Provider<SharedPreferences>(
(_) => throw UnimplementedError('Please override in ProviderScope'), (_) => throw UnimplementedError('Please override in ProviderScope'),
); );
final uiDesignProvider = Provider((ref) { class UiDesignNotifier extends StateNotifier<UiDesign> {
final sharedPreferences = ref.watch(sharedPreferencesProvider); final SharedPreferences sharedPreferences;
final stored = sharedPreferences.getString('uiDesign');
final design = UiDesign.values.where((element) => element.name == stored).firstOrNull ?? defaultUiDesign; UiDesignNotifier({required this.sharedPreferences,}) : super(UiDesign.MATERIAL) {
return design; final stored = sharedPreferences.getString('uiDesign');
}); final design = UiDesign.values.where((element) => element.name == stored).firstOrNull ?? defaultUiDesign;
Future<void> setUiDesign(Ref ref, UiDesign? design) async { state = design;
final sharedPreferences = ref.watch(sharedPreferencesProvider);
if (design != null) {
await sharedPreferences.setString('uiDesign', design.name);
} }
else {
await sharedPreferences.remove('uiDesign'); void set(UiDesign? design) async {
if (design != null) {
await sharedPreferences.setString('uiDesign', design.name);
}
else {
await sharedPreferences.remove('uiDesign');
}
state = design ?? defaultUiDesign;
} }
ref.invalidate(uiDesignProvider);
} }
final uiDesignProvider = StateNotifierProvider<UiDesignNotifier, UiDesign>(
(ref) => UiDesignNotifier(
sharedPreferences: ref.watch(sharedPreferencesProvider),
),
dependencies: [sharedPreferencesProvider],
);
class UiTimeZoneNotifier extends StateNotifier<UiTimeZone> {
final SharedPreferences sharedPreferences;
UiTimeZoneNotifier({required this.sharedPreferences,}) : super(const RoUiTimeZone()) {
final stored = sharedPreferences.getString('uiTimeZone');
if (stored != null) {
try {
state = UiTimeZone.fromSerString(stored);
}
catch (e) {
log('Invalid UiTimeZone ser: $stored, error: $e', level: 1000);
}
}
}
void set(UiTimeZone? timeZone) async {
if (timeZone != null) {
await sharedPreferences.setString('uiTimeZone', timeZone.toSerString());
}
else {
await sharedPreferences.remove('uiTimeZone');
}
state = timeZone ?? const LocalUiTimeZone();
}
}
final uiTimeZoneProvider = StateNotifierProvider<UiTimeZoneNotifier, UiTimeZone>(
(ref) => UiTimeZoneNotifier(
sharedPreferences: ref.watch(sharedPreferencesProvider),
),
dependencies: [sharedPreferencesProvider],
);
final trainInfoArgumentsProvider = Provider<TrainInfoArguments>( final trainInfoArgumentsProvider = Provider<TrainInfoArguments>(
(_) => throw UnimplementedError('Please override in ProviderScope'), (_) => throw UnimplementedError('Please override in ProviderScope'),
); );
final stationDataProvider = FutureProvider.family((ref, String stationName) async { final stationDataProvider = FutureProvider.family((ref, ViewStationArguments args) async {
final data = await getStationData(stationName); final data = await getStationData(args.stationName, args.date);
final timer = Timer(const Duration(minutes: 2), () { final timer = Timer(const Duration(minutes: 2), () {
ref.invalidateSelf(); ref.invalidateSelf();
@ -51,6 +97,9 @@ final viewStationArgumentsProvider = Provider<ViewStationArguments>(
); );
final viewStationDataProvider = Provider((ref) { final viewStationDataProvider = Provider((ref) {
final args = ref.watch(viewStationArgumentsProvider); final args = ref.watch(viewStationArgumentsProvider);
final data = ref.watch(stationDataProvider(args.stationName)); final data = ref.watch(stationDataProvider(args));
return data; return data;
}, dependencies: [viewStationArgumentsProvider, stationDataProvider]); }, dependencies: [viewStationArgumentsProvider, stationDataProvider]);
@Riverpod(keepAlive: true)
Future<TrainData> trainInfo(TrainInfoRef ref, {required String trainNumber, DateTime? date}) => getTrain(trainNumber, date: date);

120
lib/providers.g.dart

@ -0,0 +1,120 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$trainInfoHash() => r'd25aabc3ba656acf6497ec6831e11892178b22c9';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef TrainInfoRef = FutureProviderRef<TrainData>;
/// See also [trainInfo].
@ProviderFor(trainInfo)
const trainInfoProvider = TrainInfoFamily();
/// See also [trainInfo].
class TrainInfoFamily extends Family<AsyncValue<TrainData>> {
/// See also [trainInfo].
const TrainInfoFamily();
/// See also [trainInfo].
TrainInfoProvider call({
required String trainNumber,
DateTime? date,
}) {
return TrainInfoProvider(
trainNumber: trainNumber,
date: date,
);
}
@override
TrainInfoProvider getProviderOverride(
covariant TrainInfoProvider provider,
) {
return call(
trainNumber: provider.trainNumber,
date: provider.date,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'trainInfoProvider';
}
/// See also [trainInfo].
class TrainInfoProvider extends FutureProvider<TrainData> {
/// See also [trainInfo].
TrainInfoProvider({
required this.trainNumber,
this.date,
}) : super.internal(
(ref) => trainInfo(
ref,
trainNumber: trainNumber,
date: date,
),
from: trainInfoProvider,
name: r'trainInfoProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$trainInfoHash,
dependencies: TrainInfoFamily._dependencies,
allTransitiveDependencies: TrainInfoFamily._allTransitiveDependencies,
);
final String trainNumber;
final DateTime? date;
@override
bool operator ==(Object other) {
return other is TrainInfoProvider &&
other.trainNumber == trainNumber &&
other.date == date;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, trainNumber.hashCode);
hash = _SystemHash.combine(hash, date.hashCode);
return _SystemHash.finish(hash);
}
}
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

15
lib/utils/state_to_string.dart

@ -1,12 +1,7 @@
import 'package:info_tren/models.dart'; import 'package:info_tren/models.dart';
String stateToString(TrainDataState state) { String stateToString(TrainDataState state) => switch (state) {
switch(state) { TrainDataState.passing => 'trecere fără oprire',
case TrainDataState.PASSING: TrainDataState.arrival => 'sosire',
return 'trecere fără oprire'; TrainDataState.departure => 'plecare',
case TrainDataState.ARRIVAL: };
return 'sosire';
case TrainDataState.DEPARTURE:
return 'plecare';
}
}

4
linux/flutter/generated_plugin_registrant.cc

@ -6,9 +6,13 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <dynamic_color/dynamic_color_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

1
linux/flutter/generated_plugins.cmake

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color
url_launcher_linux url_launcher_linux
) )

8
linux/infotren.desktop

@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Encoding=UTF-8
Name=Info Tren
Comment=
Exec=
Icon=
Terminal=False

39
linux/install.sh

@ -0,0 +1,39 @@
#! /bin/sh
install_dir="$1"
if [ -z "$install_dir" ]; then
echo "Please specify a directory to install InfoTren to."
echo "Example:"
echo " $0 ~/infotren"
exit 3
fi
if [ ! -d "$install_dir" ]; then
if [ -d $(dirname "$install_dir") ]; then
mkdir "$install_dir"
else
echo "$install_dir doesn't exist. Please specify a directory to install InfoTren to."
echo "Example:"
echo " $0 ~/infotren"
exit 1
fi
fi
if [ ! -f ./infotren.desktop ]; then
if [ -f "$(dirname $0)/infotren.desktop" ]; then
cd "$(dirname $0)"
else
echo "Run this script from inside the infotren directory."
exit 2
fi
fi
echo "Installing InfoTren to $install_dir"
cp -r . "$install_dir"
if [ -z "$XDG_DATA_HOME" ]; then
XDG_DATA_HOME=~/.local/share
fi
if [ ! -d "$XDG_DATA_HOME/applications" ]; then
mkdir -p "$XDG_DATA_HOME/applications"
fi
echo "Installing infotren.desktop to $XDG_DATA_HOME/applications/infotren.desktop"
cat infotren.desktop | sed "s|Exec=|Exec=$install_dir/info_tren|g" > "$XDG_DATA_HOME/applications/infotren.desktop"

1
macos/Flutter/Flutter-Debug.xcconfig

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

1
macos/Flutter/Flutter-Release.xcconfig

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

40
macos/Podfile

@ -0,0 +1,40 @@
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

41
macos/Podfile.lock

@ -0,0 +1,41 @@
PODS:
- dynamic_color (0.0.2):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- package_info_plus (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
DEPENDENCIES:
- dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
EXTERNAL SOURCES:
dynamic_color:
:path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos
FlutterMacOS:
:path: Flutter/ephemeral
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
SPEC CHECKSUMS:
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
COCOAPODS: 1.12.1

71
macos/Runner.xcodeproj/project.pbxproj

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 51; objectVersion = 54;
objects = { objects = {
/* Begin PBXAggregateTarget section */ /* Begin PBXAggregateTarget section */
@ -26,6 +26,7 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
520B64251BD594DD8421C698 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE5B44A3963C12914D375BF2 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -52,6 +53,7 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0796070C1CDACAE0CA888A94 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* Info Tren.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Info Tren.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10ED2044A3C60003C045 /* Info Tren.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Info Tren.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -68,6 +70,9 @@
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
982BB43264C0B37351AE0773 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
BE5B44A3963C12914D375BF2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FCA310A2438DD41F2A155FE5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -75,6 +80,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
520B64251BD594DD8421C698 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -99,6 +105,7 @@
33CEB47122A05771004F2AC0 /* Flutter */, 33CEB47122A05771004F2AC0 /* Flutter */,
33CC10EE2044A3C60003C045 /* Products */, 33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */, D73912EC22F37F3D000D13A0 /* Frameworks */,
EB1CD289E0F53193A2CB7A73 /* Pods */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -148,10 +155,22 @@
D73912EC22F37F3D000D13A0 /* Frameworks */ = { D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BE5B44A3963C12914D375BF2 /* Pods_Runner.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EB1CD289E0F53193A2CB7A73 /* Pods */ = {
isa = PBXGroup;
children = (
FCA310A2438DD41F2A155FE5 /* Pods-Runner.debug.xcconfig */,
0796070C1CDACAE0CA888A94 /* Pods-Runner.release.xcconfig */,
982BB43264C0B37351AE0773 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -159,11 +178,13 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
85962951F5B916ED962A8FBD /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */, 33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */, 33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */, 33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */, 3399D490228B24CF009A79C7 /* ShellScript */,
907AE34F1B4BFF5379ADD9D0 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -182,7 +203,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0920; LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0930; LastUpgradeCheck = 1300;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
33CC10EC2044A3C60003C045 = { 33CC10EC2044A3C60003C045 = {
@ -235,6 +256,7 @@
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = { 3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
@ -270,6 +292,45 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
}; };
85962951F5B916ED962A8FBD /* [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;
};
907AE34F1B4BFF5379ADD9D0 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@ -344,7 +405,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11; MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
@ -423,7 +484,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11; MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx; SDKROOT = macosx;
@ -470,7 +531,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11; MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;

2
macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1000" LastUpgradeVersion = "1300"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

3
macos/Runner.xcworkspace/contents.xcworkspacedata generated

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

649
pubspec.lock

File diff suppressed because it is too large Load Diff

15
pubspec.yaml

@ -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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 2.7.9 version: 2.7.11
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=3.0.0 <4.0.0"
dependencies: dependencies:
flutter: flutter:
@ -30,18 +30,23 @@ dependencies:
url_launcher: ^6.1.5 url_launcher: ^6.1.5
flutter_hooks: ^0.18.5+1 flutter_hooks: ^0.18.5+1
hooks_riverpod: ^2.0.2 hooks_riverpod: ^2.0.2
freezed_annotation: ^2.2.0 freezed_annotation: ^2.4.1
json_annotation: ^4.7.0 json_annotation: ^4.8.1
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
fluent_ui: ^4.0.3+1 fluent_ui: ^4.0.3+1
timezone: ^0.9.0
dynamic_color: ^1.6.6
riverpod_annotation: ^2.1.1
dev_dependencies: dev_dependencies:
# flutter_test: # flutter_test:
# sdk: flutter # sdk: flutter
build_runner: ^2.1.0 build_runner: ^2.1.0
json_serializable: ^6.5.4 json_serializable: ^6.5.4
freezed: 2.2.0 freezed: ^2.4.1
flutter_lints: ^2.0.1 flutter_lints: ^2.0.1
riverpod_lint: ^1.3.2
riverpod_generator: ^2.2.3
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the

17
windows/.gitignore vendored

@ -0,0 +1,17 @@
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

101
windows/CMakeLists.txt

@ -0,0 +1,101 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(info_tren LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "info_tren")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Define build configuration option.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
# Define settings for the Profile build mode.
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)

104
windows/flutter/CMakeLists.txt

@ -0,0 +1,104 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)

17
windows/flutter/generated_plugin_registrant.cc

@ -0,0 +1,17 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
DynamicColorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

15
windows/flutter/generated_plugin_registrant.h

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

25
windows/flutter/generated_plugins.cmake

@ -0,0 +1,25 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

39
windows/runner/CMakeLists.txt

@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the build version.
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)

121
windows/runner/Runner.rc

@ -0,0 +1,121 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
#define VERSION_AS_NUMBER 1,0,0,0
#endif
#if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "ro.dcdev" "\0"
VALUE "FileDescription", "info_tren" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "info_tren" "\0"
VALUE "LegalCopyright", "Copyright (C) 2022 ro.dcdev. All rights reserved." "\0"
VALUE "OriginalFilename", "info_tren.exe" "\0"
VALUE "ProductName", "info_tren" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

61
windows/runner/flutter_window.cpp

@ -0,0 +1,61 @@
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

33
windows/runner/flutter_window.h

@ -0,0 +1,33 @@
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_

43
windows/runner/main.cpp

@ -0,0 +1,43 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"Info Tren", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}

16
windows/runner/resource.h

@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

BIN
windows/runner/resources/app_icon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

20
windows/runner/runner.exe.manifest

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>

64
windows/runner/utils.cpp

@ -0,0 +1,64 @@
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr);
std::string utf8_string;
if (target_length == 0 || target_length > utf8_string.max_size()) {
return utf8_string;
}
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, utf8_string.data(),
target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}

19
windows/runner/utils.h

@ -0,0 +1,19 @@
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_

245
windows/runner/win32_window.cpp

@ -0,0 +1,245 @@
#include "win32_window.h"
#include <flutter_windows.h>
#include "resource.h"
namespace {
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
FreeLibrary(user32_module);
}
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
return OnCreate();
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}

98
windows/runner/win32_window.h

@ -0,0 +1,98 @@
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates and shows a win32 window with |title| and position and size using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size to will treat the width height passed in to this function
// as logical pixels and scale to appropriate for the default monitor. Returns
// true if the window was created successfully.
bool CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size);
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responsponds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_
Loading…
Cancel
Save