Compare commits

..

No commits in common. 'master' and 'v2.6.0' have entirely different histories.

  1. 24
      .metadata
  2. 6
      .vscode/launch.json
  3. 65
      CHANGELOG.TXT
  4. 122
      CHANGELOG.txt
  5. 24
      android/app/build.gradle
  6. 2
      android/app/src/debug/AndroidManifest.xml
  7. 3
      android/app/src/main/AndroidManifest.xml
  8. 2
      android/app/src/main/kotlin/xyz/dcdevelop/infotren/MainActivity.kt
  9. 2
      android/app/src/profile/AndroidManifest.xml
  10. 6
      android/build.gradle
  11. 2
      android/gradle/wrapper/gradle-wrapper.properties
  12. 1
      assets/lines/atc.json
  13. 1
      assets/lines/cfr.json
  14. 6
      assets/lines/files.txt
  15. 1
      assets/lines/interregional.json
  16. 1
      assets/lines/rc.json
  17. 1
      assets/lines/st.json
  18. 1
      assets/lines/tfc.json
  19. 216
      codemagic.yaml
  20. 2
      ios/Flutter/AppFrameworkInfo.plist
  21. 2
      ios/Podfile
  22. 34
      ios/Podfile.lock
  23. 82
      ios/Runner.xcodeproj/project.pbxproj
  24. 2
      ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  25. 3
      ios/Runner.xcworkspace/contents.xcworkspacedata
  26. 2
      ios/Runner/Info.plist
  27. 2
      lib/api/common.dart
  28. 18
      lib/api/releases.dart
  29. 12
      lib/api/station_data.dart
  30. 4
      lib/api/stations.dart
  31. 6
      lib/api/train_data.dart
  32. 11
      lib/api/trains.dart
  33. 59
      lib/components/badge/badge.dart
  34. 80
      lib/components/badge/badge_cupertino.dart
  35. 80
      lib/components/badge/badge_fluent.dart
  36. 79
      lib/components/badge/badge_material.dart
  37. 8
      lib/components/cupertino_divider.dart
  38. 71
      lib/components/cupertino_listtile.dart
  39. 8
      lib/components/future_display.dart
  40. 25
      lib/components/loading/loading.dart
  41. 7
      lib/components/loading/loading_cupertino.dart
  42. 26
      lib/components/loading/loading_fluent.dart
  43. 6
      lib/components/loading/loading_material.dart
  44. 45
      lib/components/refresh_future_builder.dart
  45. 184
      lib/components/select_train_suggestions/select_train_suggestions.dart
  46. 24
      lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart
  47. 85
      lib/components/select_train_suggestions/select_train_suggestions_fluent.dart
  48. 20
      lib/components/select_train_suggestions/select_train_suggestions_material.dart
  49. 13
      lib/components/slim_app_bar.dart
  50. 2
      lib/components/sliver_persistent_header_padding.dart
  51. 39
      lib/components/train_id_text_span.dart
  52. 211
      lib/main.dart
  53. 14
      lib/models.dart
  54. 83
      lib/models/changelog_entry.dart
  55. 18
      lib/models/station_arrdep.dart
  56. 241
      lib/models/station_arrdep.freezed.dart
  57. 23
      lib/models/station_arrdep.g.dart
  58. 73
      lib/models/station_data.dart
  59. 239
      lib/models/station_data.freezed.dart
  60. 75
      lib/models/station_data.g.dart
  61. 17
      lib/models/station_status.dart
  62. 210
      lib/models/station_status.freezed.dart
  63. 23
      lib/models/station_status.g.dart
  64. 19
      lib/models/station_train.dart
  65. 268
      lib/models/station_train.freezed.dart
  66. 28
      lib/models/station_train.g.dart
  67. 18
      lib/models/stations_result.dart
  68. 179
      lib/models/stations_result.freezed.dart
  69. 6
      lib/models/stations_result.g.dart
  70. 375
      lib/models/train_data.dart
  71. 2365
      lib/models/train_data.freezed.dart
  72. 217
      lib/models/train_data.g.dart
  73. 42
      lib/models/train_operator_lines.dart
  74. 42
      lib/models/train_operator_lines.g.dart
  75. 15
      lib/models/trains_result.dart
  76. 187
      lib/models/trains_result.freezed.dart
  77. 21
      lib/models/trains_result.g.dart
  78. 11
      lib/models/ui_design.dart
  79. 94
      lib/models/ui_timezone.dart
  80. 80
      lib/pages/about/about_page.dart
  81. 137
      lib/pages/about/about_page_cupertino.dart
  82. 198
      lib/pages/about/about_page_fluent.dart
  83. 199
      lib/pages/about/about_page_material.dart
  84. 64
      lib/pages/main/main_page.dart
  85. 47
      lib/pages/main/main_page_cupertino.dart
  86. 89
      lib/pages/main/main_page_fluent.dart
  87. 42
      lib/pages/main/main_page_material.dart
  88. 38
      lib/pages/settings/setings_page.dart
  89. 96
      lib/pages/settings/settings_page_cupertino.dart
  90. 65
      lib/pages/settings/settings_page_fluent.dart
  91. 66
      lib/pages/settings/settings_page_material.dart
  92. 46
      lib/pages/station_arrdep_page/select_station/select_station.dart
  93. 11
      lib/pages/station_arrdep_page/select_station/select_station_cupertino.dart
  94. 59
      lib/pages/station_arrdep_page/select_station/select_station_fluent.dart
  95. 14
      lib/pages/station_arrdep_page/select_station/select_station_material.dart
  96. 118
      lib/pages/station_arrdep_page/view_station/view_station.dart
  97. 89
      lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart
  98. 264
      lib/pages/station_arrdep_page/view_station/view_station_fluent.dart
  99. 261
      lib/pages/station_arrdep_page/view_station/view_station_material.dart
  100. 104
      lib/pages/train_info_page/select_train/select_train.dart
  101. Some files were not shown because too many files have changed in this diff Show More

24
.metadata

@ -1,30 +1,10 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled. # This file should be version controlled and should not be manually edited.
version: version:
revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 revision: b712a172f9694745f50505c93340883493b505e5
channel: stable channel: stable
project_type: app project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: windows
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

6
.vscode/launch.json vendored

@ -7,11 +7,7 @@
{ {
"name": "Current Device", "name": "Current Device",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart"
"args": [
"--dart-define",
"DOWNLOAD=apk"
]
}, },
{ {
"name": "info_tren (profile mode)", "name": "info_tren (profile mode)",

65
CHANGELOG.TXT

@ -0,0 +1,65 @@
v2.6.0
Added ability to view yesterday data for trains that didn't depart yet today.
Fixed iOS status bar color.
v2.5.0
Initial arrivals/departures support
v2.4.1
Fixed DateTime (UTC -> local)
v2.4.0
Moved to api v2
Added support for any train number, including train numbers starting with 0
v2.3.1
Fixed badge background when arrival is known but not departure
v2.3.0
Added pull to refresh
v2.2.0
Added refresh button on error
v2.1.1
Fixed Android build
Switched versioning format
v2.0.7
Switched from WebView to API
Updated app to latest Flutter
Tweaks
v2.0.6
Brought feature parity with iOS _(except for v2.0.2, which is iOS specific)_.
v2.0.5
- increased font weight on iOS
- added support for system bolt font request on iOS
v2.0.4
- added original time in case of delay for iOS
+ will be for Android soon
+ in case there is a delay, the original time will be shown with
a stroke through it and the new time will be shown below
v2.0.3
- added km badge colour for iOS
+ will be added for Android soon
+ green for being on time
+ yellow for a non planned stop
+ red for a delay
v2.0.2
- added translucency to the navigation bar on iOS
v2.0.1
- added a little separation between the arrows and the text in the stations list
- fine tuned the positioning, centering items when they are supposed to be centered
- changed text from "sosește" to "sosire", in order to match "plecare"
v2.0.0
Rewritten!
- separate UI for Android and iOS
- uses WebView to get data on device instead of relying on server

122
CHANGELOG.txt

@ -1,122 +0,0 @@
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
Add Fluent UI for Windows and Linux.
Add split view in landscape when viewing a train.
Add error handling and auto refresh when viewing a station's arrivals/departures.
General code fixes and migration (freezed, riverpod).
v2.7.8
Added cancelled trains in departures/arrivals board.
Selecting train in departures/arrivels board chooses appropriate departure date.
Temporarily switched all platforms to Material.
v2.7.7
Improved departures/arrivals page:
- badge for platform (due to limitations in data, platform in only known when a train arrives/departs)
- time deviations shown (delays or arriving early)
Moved to API v3 for station data.
v2.7.6
Transitioned to Material 3.
Redesigned main page on Material.
On Android (Material), tapping station card in train information screen opens departures/arrivals board.
Added past tense to trains already arrived/departed.
Fixed download button on Android.
v2.7.5
Added about page and in-app changelog.
On Android, added download buttons.
v2.7.4
Addressed Android 12 component exporting rule.
See: https://developer.android.com/about/versions/12/behavior-changes-12#exported
v2.7.3
Added Android APK signing.
v2.7.2
Fixed alignment of station names in train screen.
v2.7.1
Switched train suggestions list from hardcoded data to server data.
Added Linux build files.
v2.7.0
Changed domain name for server providing the data.
Changed bundle name accordingly.
v2.6.0
Added ability to view yesterday data for trains that didn't depart yet today.
Fixed iOS status bar color.
v2.5.0
Initial arrivals/departures support
v2.4.1
Fixed DateTime (UTC -> local)
v2.4.0
Moved to api v2
Added support for any train number, including train numbers starting with 0
v2.3.1
Fixed badge background when arrival is known but not departure
v2.3.0
Added pull to refresh
v2.2.0
Added refresh button on error
v2.1.1
Fixed Android build
Switched versioning format
v2.0.7
Switched from WebView to API
Updated app to latest Flutter
Tweaks
v2.0.6
Brought feature parity with iOS _(except for v2.0.2, which is iOS specific)_.
v2.0.5
- increased font weight on iOS
- added support for system bolt font request on iOS
v2.0.4
- added original time in case of delay for iOS
+ will be for Android soon
+ in case there is a delay, the original time will be shown with
a stroke through it and the new time will be shown below
v2.0.3
- added km badge colour for iOS
+ will be added for Android soon
+ green for being on time
+ yellow for a non planned stop
+ red for a delay
v2.0.2
- added translucency to the navigation bar on iOS
v2.0.1
- added a little separation between the arrows and the text in the stations list
- fine tuned the positioning, centering items when they are supposed to be centered
- changed text from "sosește" to "sosire", in order to match "plecare"
v2.0.0
Rewritten!
- separate UI for Android and iOS
- uses WebView to get data on device instead of relying on server

24
android/app/build.gradle

@ -25,14 +25,8 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android { android {
compileSdkVersion 33 compileSdkVersion 30
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -49,24 +43,18 @@ android {
defaultConfig { defaultConfig {
// 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 "xyz.dcdevelop.infotren"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 33 targetSdkVersion 30
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes { buildTypes {
release { release {
signingConfig signingConfigs.release // TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
} }
} }
} }

2
android/app/src/debug/AndroidManifest.xml

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

3
android/app/src/main/AndroidManifest.xml

@ -1,12 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ro.dcdev.infotren"> package="xyz.dcdevelop.infotren">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="Info Tren" android:label="Info Tren"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

2
android/app/src/main/kotlin/xyz/dcdevelop/infotren/MainActivity.kt

@ -1,4 +1,4 @@
package ro.dcdev.infotren package xyz.dcdevelop.infotren
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

2
android/app/src/profile/AndroidManifest.xml

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

6
android/build.gradle

@ -1,12 +1,12 @@
buildscript { buildscript {
ext.kotlin_version = '1.7.10' ext.kotlin_version = '1.3.50'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.android.tools.build:gradle:4.1.0'
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')
} }
tasks.register("clean", Delete) { task clean(type: 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-7.6.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

1
assets/lines/atc.json

@ -0,0 +1 @@
{"short_name":"ATC","operator":"Astra Trans Carpatic","data_export":"20171212","valabil":{"de_la":"20171210","pana_la":"20181208"},"versiune":"1","trenuri":[{"rang":"IR","numar":"15510","numar_intern":15510},{"rang":"IR","numar":"15511","numar_intern":15511},{"rang":"IR","numar":"15512","numar_intern":15512},{"rang":"IR","numar":"15513*","numar_intern":15513},{"rang":"IR","numar":"15513","numar_intern":15513},{"rang":"IR","numar":"15514","numar_intern":15514},{"rang":"IR","numar":"15514","numar_intern":15514},{"rang":"IR","numar":"15516","numar_intern":15516},{"rang":"IR","numar":"15520","numar_intern":15520},{"rang":"IR","numar":"15521*","numar_intern":15521},{"rang":"IR","numar":"15521","numar_intern":15521},{"rang":"IR","numar":"15522","numar_intern":15522},{"rang":"IR","numar":"15522","numar_intern":15522},{"rang":"IR","numar":"15523","numar_intern":15523},{"rang":"IR","numar":"15523","numar_intern":15523},{"rang":"IR","numar":"15527","numar_intern":15527},{"rang":"IR","numar":"15528","numar_intern":15528},{"rang":"IR","numar":"15529","numar_intern":15529},{"rang":"IR","numar":"15531","numar_intern":15531},{"rang":"IR","numar":"15532","numar_intern":15532},{"rang":"IR","numar":"15533","numar_intern":15533},{"rang":"IR","numar":"15533e","numar_intern":15533},{"rang":"IR","numar":"15534","numar_intern":15534},{"rang":"IR","numar":"15535","numar_intern":15535},{"rang":"IR","numar":"15536","numar_intern":15536},{"rang":"IR","numar":"15537-2","numar_intern":15537},{"rang":"IR","numar":"15538","numar_intern":15538},{"rang":"IR","numar":"15540","numar_intern":15540},{"rang":"IR","numar":"15541","numar_intern":15541},{"rang":"IR","numar":"15541","numar_intern":15541},{"rang":"IR","numar":"15542","numar_intern":15542},{"rang":"IR","numar":"15542","numar_intern":15542},{"rang":"IR","numar":"15543","numar_intern":15543},{"rang":"IR","numar":"15544","numar_intern":15544},{"rang":"IR","numar":"15545","numar_intern":15545},{"rang":"IR","numar":"15546*","numar_intern":15546},{"rang":"IR","numar":"15546","numar_intern":15546},{"rang":"IR","numar":"15547","numar_intern":15547},{"rang":"IR","numar":"15548","numar_intern":15548},{"rang":"IR","numar":"15549*","numar_intern":15549},{"rang":"IR","numar":"15549b","numar_intern":15549},{"rang":"IR","numar":"15551","numar_intern":15551},{"rang":"IR","numar":"15552","numar_intern":15552},{"rang":"IR","numar":"15553","numar_intern":15553},{"rang":"IR","numar":"15581","numar_intern":15581},{"rang":"IR","numar":"15582","numar_intern":15582},{"rang":"IR","numar":"15582***","numar_intern":15582},{"rang":"IR","numar":"15583","numar_intern":15583},{"rang":"IR","numar":"15590","numar_intern":15590},{"rang":"IR","numar":"15591","numar_intern":15591},{"rang":"IR","numar":"15592","numar_intern":15592},{"rang":"IR","numar":"15593","numar_intern":15593},{"rang":"R","numar":"*P18801","numar_intern":null},{"rang":"IR","numar":"*15546","numar_intern":null},{"rang":"R","numar":"**P18801","numar_intern":null},{"rang":"IR","numar":"*15591","numar_intern":null},{"rang":"R","numar":"**P18800","numar_intern":null},{"rang":"IR","numar":"15593","numar_intern":15593},{"rang":"IR","numar":"15594","numar_intern":15594},{"rang":"IR","numar":"15595-2","numar_intern":15595},{"rang":"R","numar":"18800","numar_intern":18800},{"rang":"R","numar":"18801","numar_intern":18801},{"rang":"IR","numar":"18826","numar_intern":18826},{"rang":"IR","numar":"18851","numar_intern":18851},{"rang":"IR","numar":"18852","numar_intern":18852}]}

1
assets/lines/cfr.json

File diff suppressed because one or more lines are too long

6
assets/lines/files.txt

@ -0,0 +1,6 @@
atc.json
cfr.json
interregional.json
rc.json
st.json
tfc.json

1
assets/lines/interregional.json

File diff suppressed because one or more lines are too long

1
assets/lines/rc.json

File diff suppressed because one or more lines are too long

1
assets/lines/st.json

@ -0,0 +1 @@
{"short_name":"Softrans","operator":"Softrans S.R.L.","data_export":"20181212","valabil":{"de_la":"20181209","pana_la":"20191214"},"versiune":"1","trenuri":[{"rang":"IR","numar":"15931-2","numar_intern":15931},{"rang":"IR","numar":"15932","numar_intern":15932},{"rang":"IR","numar":"15933-2","numar_intern":15933},{"rang":"IR","numar":"15934","numar_intern":15934},{"rang":"IR","numar":"15935-2","numar_intern":15935},{"rang":"IR","numar":"15936","numar_intern":15936},{"rang":"IR","numar":"15982","numar_intern":15982},{"rang":"IR","numar":"15984","numar_intern":15984}]}

1
assets/lines/tfc.json

File diff suppressed because one or more lines are too long

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>11.0</string> <string>9.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, '11.0' # platform :ios, '9.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

@ -1,34 +0,0 @@
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

82
ios/Runner.xcodeproj/project.pbxproj

@ -13,7 +13,6 @@
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 */
@ -32,11 +31,7 @@
/* 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>"; };
@ -54,7 +49,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
AF5528149967EA996B5AA109 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -78,8 +72,6 @@
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
B55F9B76DFEAB456725329A0 /* Pods */,
E56598AA51C5533E6B51BD5A /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -106,25 +98,6 @@
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 */
@ -132,14 +105,12 @@
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 = (
); );
@ -156,7 +127,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1300; LastUpgradeCheck = 1020;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
@ -198,28 +169,6 @@
/* 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;
@ -248,23 +197,6 @@
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 */
@ -340,7 +272,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 = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -364,7 +296,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 2.0.7; MARKETING_VERSION = 2.0.7;
PRODUCT_BUNDLE_IDENTIFIER = ro.dcdev.infotren; PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -419,7 +351,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 = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -468,7 +400,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 = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -494,7 +426,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 2.0.7; MARKETING_VERSION = 2.0.7;
PRODUCT_BUNDLE_IDENTIFIER = ro.dcdev.infotren; PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -518,7 +450,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 2.0.7; MARKETING_VERSION = 2.0.7;
PRODUCT_BUNDLE_IDENTIFIER = ro.dcdev.infotren; PRODUCT_BUNDLE_IDENTIFIER = xyz.dcdevelop.infotren;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

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 = "1300" LastUpgradeVersion = "1020"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

3
ios/Runner.xcworkspace/contents.xcworkspacedata generated

@ -4,7 +4,4 @@
<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,7 +43,5 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict> </dict>
</plist> </plist>

2
lib/api/common.dart

@ -1 +1 @@
const authority = 'scraper.infotren.dcdev.ro'; const authority = 'scraper.infotren.dcdevelop.xyz';

18
lib/api/releases.dart

@ -1,18 +0,0 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:info_tren/models.dart';
import 'package:info_tren/utils/iterable_extensions.dart';
Future<List<ChangelogEntry>> getRemoteReleases() async {
final Uri uri = Uri.parse('https://gitea.dcdev.ro/api/v1/repos/kbruen/info_tren/releases');
final response = await http.get(uri);
final json = jsonDecode(response.body) as List<dynamic>;
return json.map((e) => ChangelogEntry(
version: ChangelogVersion.parse(e['tag_name']),
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,
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();
}

12
lib/api/station_data.dart

@ -2,13 +2,9 @@ import 'dart:convert';
import 'package:http/http.dart' as http; 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/station_data.dart';
Future<StationData> getStationData(String stationName, [DateTime? date]) async { Future<StationData> getStationData(String stationName) async {
final uri = Uri.https(authority, 'v3/stations/$stationName'); final response = await http.get(Uri.https(authority, 'v2/station/$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));
} }

4
lib/api/stations.dart

@ -2,10 +2,10 @@ import 'dart:convert';
import 'package:http/http.dart' as http; 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/stations_result.dart';
Future<List<StationsResult>> get stations async { Future<List<StationsResult>> get stations async {
final result = await http.get(Uri.https(authority, 'v2/stations')); final result = await http.get(Uri.https(authority, 'v2/stations'));
final data = jsonDecode(result.body) as List<dynamic>; final data = jsonDecode(result.body) as List<dynamic>;
return data.map((e) => StationsResult.fromJson(e)).toList(growable: false,); return data.map((e) => StationsResult.fromJson(e)).toList(growable: false,);
} }

6
lib/api/train_data.dart

@ -1,11 +1,11 @@
import 'package:http/http.dart' as http; 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/train_data.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, 'v3/trains/$trainNumber', { final response = await http.get(Uri.https(authority, 'v2/train/$trainNumber', {
'date': date.toUtc().toIso8601String(), 'date': date.toIso8601String(),
}),); }),);
return trainDataFromJson(response.body); return trainDataFromJson(response.body);
} }

11
lib/api/trains.dart

@ -1,11 +0,0 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:info_tren/api/common.dart';
import 'package:info_tren/models.dart';
Future<List<TrainsResult>> get trains async {
final result = await http.get(Uri.https(authority, 'v2/trains'));
final data = jsonDecode(result.body) as List<dynamic>;
return data.map((e) => TrainsResult.fromJson(e)).toList(growable: false,);
}

59
lib/components/badge/badge.dart

@ -1,59 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/badge/badge_cupertino.dart';
import 'package:info_tren/components/badge/badge_fluent.dart';
import 'package:info_tren/components/badge/badge_material.dart';
import 'package:info_tren/models.dart';
import 'package:info_tren/providers.dart';
class Badge extends ConsumerWidget {
final String text;
final String caption;
final bool isNotScheduled;
final bool isOnTime;
final bool isDelayed;
const Badge({
super.key,
required this.text,
required this.caption,
this.isNotScheduled = false,
this.isOnTime = false,
this.isDelayed = false,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider);
switch (uiDesign) {
case UiDesign.MATERIAL:
return MaterialBadge(
text: text,
caption: caption,
isNotScheduled: isNotScheduled,
isOnTime: isOnTime,
isDelayed: isDelayed,
);
case UiDesign.CUPERTINO:
return CupertinoBadge(
text: text,
caption: caption,
isNotScheduled: isNotScheduled,
isOnTime: isOnTime,
isDelayed: isDelayed,
);
case UiDesign.FLUENT:
return FluentBadge(
text: text,
caption: caption,
isNotScheduled: isNotScheduled,
isOnTime: isOnTime,
isDelayed: isDelayed,
);
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}

80
lib/components/badge/badge_cupertino.dart

@ -1,80 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:info_tren/pages/train_info_page/train_info_constants.dart';
class CupertinoBadge extends StatelessWidget {
final String text;
final String caption;
final bool isNotScheduled;
final bool isOnTime;
final bool isDelayed;
const CupertinoBadge({
required this.text,
required this.caption,
this.isNotScheduled = false,
this.isOnTime = false,
this.isDelayed = false,
super.key,
});
@override
Widget build(BuildContext context) {
Color foregroundColor = foregroundWhite;
Color? backgroundColor;
if (isNotScheduled) {
foregroundColor = const Color.fromRGBO(225, 175, 30, 1);
backgroundColor = const Color.fromRGBO(80, 40, 10, 1);
}
else if (isOnTime) {
foregroundColor = const Color.fromRGBO(130, 175, 65, 1);
backgroundColor = const Color.fromRGBO(40, 80, 10, 1);
}
else if (isDelayed) {
foregroundColor = const Color.fromRGBO(225, 75, 30, 1);
backgroundColor = const Color.fromRGBO(80, 20, 10, 1);
}
return Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 2,
color: foregroundColor,
),
color: backgroundColor,
// color: CupertinoColors.activeOrange,
),
width: 48,
height: 48,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Center(
child: Text(
text,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 20,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
color: MediaQuery.of(context).boldText ? foregroundWhite : foregroundColor,
),
textAlign: TextAlign.center,
),
),
),
Text(
caption,
style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 12,
color: MediaQuery.of(context).boldText ? foregroundWhite : foregroundColor,
),
),
],
),
),
);
}
}

80
lib/components/badge/badge_fluent.dart

@ -1,80 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
class FluentBadge extends StatelessWidget {
final String text;
final String caption;
final bool isNotScheduled;
final bool isOnTime;
final bool isDelayed;
const FluentBadge({
required this.text,
required this.caption,
this.isNotScheduled = false,
this.isOnTime = false,
this.isDelayed = false,
super.key,
});
@override
Widget build(BuildContext context) {
Color foregroundColor = FluentTheme.of(context).activeColor;
Color? backgroundColor;
if (isNotScheduled) {
foregroundColor = const Color.fromRGBO(225, 175, 30, 1);
backgroundColor = const Color.fromRGBO(80, 40, 10, 1);
}
else if (isOnTime) {
foregroundColor = const Color.fromRGBO(130, 175, 65, 1);
backgroundColor = const Color.fromRGBO(40, 80, 10, 1);
}
else if (isDelayed) {
foregroundColor = const Color.fromRGBO(225, 75, 30, 1);
backgroundColor = const Color.fromRGBO(80, 20, 10, 1);
}
return Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 2,
color: foregroundColor,
),
color: backgroundColor,
// color: CupertinoColors.activeOrange,
),
width: 48,
height: 48,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Center(
child: Text(
text,
style: FluentTheme.of(context).typography.bodyLarge?.copyWith(
fontSize: 20,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
color: MediaQuery.of(context).boldText ? Colors.white : foregroundColor,
),
textAlign: TextAlign.center,
),
),
),
Text(
caption,
style: FluentTheme.of(context).typography.body?.copyWith(
fontSize: 12,
color: MediaQuery.of(context).boldText ? Colors.white : foregroundColor,
),
),
],
),
),
);
}
}

79
lib/components/badge/badge_material.dart

@ -1,79 +0,0 @@
import 'package:flutter/material.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart';
class MaterialBadge extends StatelessWidget {
final String text;
final String caption;
final bool isNotScheduled;
final bool isOnTime;
final bool isDelayed;
const MaterialBadge({
required this.text,
required this.caption,
this.isNotScheduled = false,
this.isOnTime = false,
this.isDelayed = false,
super.key,
});
@override
Widget build(BuildContext context) {
Color foregroundColor = Colors.white70;
Color? backgroundColor;
if (isNotScheduled) {
foregroundColor = Colors.orange.shade300;
backgroundColor = Colors.orange.shade900.withOpacity(0.3);
}
else if (isOnTime) {
foregroundColor = Colors.green.shade300;
backgroundColor = Colors.green.shade900.withOpacity(0.3);
}
else if (isDelayed) {
foregroundColor = Colors.red.shade300;
backgroundColor = Colors.red.shade900.withOpacity(0.3);
}
return Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 2,
color: foregroundColor,
),
color: backgroundColor,
),
width: isSmallScreen(context) ? 42 : 48,
height: isSmallScreen(context) ? 42 : 48,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Center(
child: Text(
text,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: isSmallScreen(context) ? 16 : 20,
fontWeight: MediaQuery.of(context).boldText ? FontWeight.w400 : FontWeight.w200,
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor,
),
textAlign: TextAlign.center,
),
),
),
Text(
caption,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 10,
color: MediaQuery.of(context).boldText ? Colors.white70 : foregroundColor,
),
),
],
),
),
);
}
}

8
lib/components/cupertino_divider.dart

@ -4,8 +4,8 @@ import 'package:info_tren/pages/train_info_page/train_info_constants.dart';
class CupertinoDivider extends StatelessWidget { class CupertinoDivider extends StatelessWidget {
final Color color; final Color color;
const CupertinoDivider({Key? key, Color? color}): CupertinoDivider({Key? key, Color? color}):
color = color ?? foregroundDarkGrey, color = color ?? FOREGROUND_DARK_GREY,
super(key: key); super(key: key);
@override @override
@ -33,8 +33,8 @@ class CupertinoDivider extends StatelessWidget {
class CupertinoVerticalDivider extends StatelessWidget { class CupertinoVerticalDivider extends StatelessWidget {
final Color color; final Color color;
const CupertinoVerticalDivider({Key? key, Color? color}): CupertinoVerticalDivider({Key? key, Color? color}):
color = color ?? foregroundDarkGrey, color = color ?? FOREGROUND_DARK_GREY,
super(key: key); super(key: key);
@override @override

71
lib/components/cupertino_listtile.dart

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

8
lib/components/future_display.dart

@ -1,6 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/utils/default_ui_design.dart'; import 'package:info_tren/utils/default_ui_design.dart';
class FutureDisplay<T> extends StatelessWidget { class FutureDisplay<T> extends StatelessWidget {
@ -9,7 +9,7 @@ class FutureDisplay<T> extends StatelessWidget {
final Widget Function<T>(BuildContext context, T data) builder; final Widget Function<T>(BuildContext context, T data) builder;
final Widget Function(BuildContext context, Object error, StackTrace? st)? errorBuilder; final Widget Function(BuildContext context, Object error, StackTrace? st)? errorBuilder;
const FutureDisplay({Key? key, required this.future, required this.builder, this.errorBuilder, this.uiDesign}): super(key: key); FutureDisplay({Key? key, required this.future, required this.builder, this.errorBuilder, this.uiDesign}): super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -24,10 +24,10 @@ class FutureDisplay<T> extends StatelessWidget {
Widget loadingWidget; Widget loadingWidget;
switch (uiDesign) { switch (uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
loadingWidget = const CircularProgressIndicator(); loadingWidget = CircularProgressIndicator();
break; break;
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
loadingWidget = const CupertinoActivityIndicator(); loadingWidget = CupertinoActivityIndicator();
break; break;
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);

25
lib/components/loading/loading.dart

@ -1,27 +1,24 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/loading/loading_cupertino.dart'; import 'package:info_tren/components/loading/loading_cupertino.dart';
import 'package:info_tren/components/loading/loading_fluent.dart';
import 'package:info_tren/components/loading/loading_material.dart'; import 'package:info_tren/components/loading/loading_material.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/utils/default_ui_design.dart';
class Loading extends ConsumerWidget { class Loading extends StatelessWidget {
static const defaultText = 'Loading...'; static const DEFAULT_TEXT = 'Loading...';
final UiDesign? uiDesign;
final String? text; final String? text;
const Loading({ super.key, this.text, }); const Loading({ Key? key, this.text, this.uiDesign }) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context) {
final uiDesign = ref.watch(uiDesignProvider); final uiDesign = this.uiDesign ?? defaultUiDesign;
switch (uiDesign) { switch (uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
return LoadingMaterial(text: text ?? defaultText,); return LoadingMaterial(text: text ?? DEFAULT_TEXT,);
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return LoadingCupertino(text: text ?? defaultText,); return LoadingCupertino(text: text ?? DEFAULT_TEXT,);
case UiDesign.FLUENT:
return LoadingFluent(text: text ?? defaultText,);
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
@ -30,5 +27,5 @@ class Loading extends ConsumerWidget {
abstract class LoadingCommon extends StatelessWidget { abstract class LoadingCommon extends StatelessWidget {
final String text; final String text;
const LoadingCommon({required this.text, super.key,}); LoadingCommon({required this.text});
} }

7
lib/components/loading/loading_cupertino.dart

@ -1,8 +1,9 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/loading/loading.dart';
class LoadingCupertino extends LoadingCommon { class LoadingCupertino extends LoadingCommon {
const LoadingCupertino({required super.text, super.key,}); LoadingCupertino({required String text}) : super(text: text,);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -11,8 +12,8 @@ class LoadingCupertino extends LoadingCommon {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: CupertinoActivityIndicator(), child: CupertinoActivityIndicator(),
), ),
Padding( Padding(

26
lib/components/loading/loading_fluent.dart

@ -1,26 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:info_tren/components/loading/loading.dart';
class LoadingFluent extends LoadingCommon {
const LoadingFluent({required super.text, super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: ProgressRing(),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(text),
),
],
),
);
}
}

6
lib/components/loading/loading_material.dart

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/loading/loading.dart';
class LoadingMaterial extends LoadingCommon { class LoadingMaterial extends LoadingCommon {
const LoadingMaterial({required super.text, super.key}); LoadingMaterial({required String text}) : super(text: text,);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -11,8 +11,8 @@ class LoadingMaterial extends LoadingCommon {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
Padding( Padding(

45
lib/components/refresh_future_builder.dart

@ -1,5 +1,4 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class RefreshFutureBuilder<T> extends StatefulWidget { class RefreshFutureBuilder<T> extends StatefulWidget {
final Future<T> Function()? futureCreator; final Future<T> Function()? futureCreator;
@ -20,7 +19,7 @@ class _RefreshFutureBuilderState<T> extends State<RefreshFutureBuilder<T>> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
snapshot = widget.initialData != null ? RefreshFutureBuilderSnapshot.initial(widget.initialData as T) : const RefreshFutureBuilderSnapshot.nothing(); snapshot = widget.initialData != null ? RefreshFutureBuilderSnapshot.initial(widget.initialData!) : RefreshFutureBuilderSnapshot.nothing();
} }
@override @override
@ -40,7 +39,7 @@ class _RefreshFutureBuilderState<T> extends State<RefreshFutureBuilder<T>> {
setState(() { setState(() {
switch (snapshot.state) { switch (snapshot.state) {
case RefreshFutureBuilderState.none: case RefreshFutureBuilderState.none:
snapshot = const RefreshFutureBuilderSnapshot.waiting(); snapshot = RefreshFutureBuilderSnapshot.waiting();
break; break;
case RefreshFutureBuilderState.initial: case RefreshFutureBuilderState.initial:
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data); snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data);
@ -48,7 +47,7 @@ class _RefreshFutureBuilderState<T> extends State<RefreshFutureBuilder<T>> {
case RefreshFutureBuilderState.waiting: case RefreshFutureBuilderState.waiting:
return; return;
case RefreshFutureBuilderState.error: case RefreshFutureBuilderState.error:
snapshot = const RefreshFutureBuilderSnapshot.waiting(); snapshot = RefreshFutureBuilderSnapshot.waiting();
break; break;
case RefreshFutureBuilderState.done: case RefreshFutureBuilderState.done:
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data); snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data);
@ -135,41 +134,3 @@ enum RefreshFutureBuilderState {
refreshing, refreshing,
refreshError, refreshError,
} }
class RefreshFutureBuilderProviderAdapter<T> extends ConsumerWidget {
final Provider<AsyncValue<T>> futureProvider;
final Future Function()? refresh;
final Widget Function(BuildContext context, Future Function() refresh, Future Function(Future<T> Function()) replaceFuture, RefreshFutureBuilderSnapshot<T> snapshot) builder;
const RefreshFutureBuilderProviderAdapter({required this.futureProvider, required this.builder, this.refresh, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final value = ref.watch(futureProvider);
return builder(
context,
refresh ?? () async {
ref.invalidate(futureProvider);
},
(_) => throw UnimplementedError('Cannot replace the future when adapting a FutureProvider'),
value.when(
data: (data) => value.isLoading || value.isRefreshing
? RefreshFutureBuilderSnapshot.refresh(data)
: RefreshFutureBuilderSnapshot.withData(data),
error: (error, st) => value.isLoading || value.isRefreshing
? RefreshFutureBuilderSnapshot.refreshError(value.hasValue ? value.value : null, value.error, value.stackTrace)
: RefreshFutureBuilderSnapshot.withError(error, st),
loading: () {
if (value.hasValue) {
return RefreshFutureBuilderSnapshot.refresh(value.value, value.error, value.stackTrace);
}
else if (value.hasError) {
return RefreshFutureBuilderSnapshot.refreshError(value.value, value.error, value.stackTrace);
}
return const RefreshFutureBuilderSnapshot.waiting();
},
),
);
}
}

184
lib/components/select_train_suggestions/select_train_suggestions.dart

@ -1,75 +1,113 @@
import 'dart:convert';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.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';
import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_fluent.dart';
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/train_operator_lines.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/utils/default_ui_design.dart';
import 'package:tuple/tuple.dart';
class SelectTrainSuggestions extends ConsumerWidget { class SelectTrainSuggestions extends StatefulWidget {
final List<TrainsResult> choices; final UiDesign? uiDesign;
final String? currentInput; final String userInput;
final void Function(String trainNumber) onTrainSelected; final void Function(String trainNumber) onTrainSelected;
const SelectTrainSuggestions({required this.choices, this.currentInput, required this.onTrainSelected, super.key, }); const SelectTrainSuggestions({ Key? key, required this.uiDesign, required this.userInput, required this.onTrainSelected }) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider);
@override
SelectTrainSuggestionsState createState() {
final uiDesign = this.uiDesign ?? defaultUiDesign;
switch(uiDesign) { switch(uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
return SelectTrainSuggestionsMaterial( return SelectTrainSuggestionsStateMaterial();
choices: choices,
onTrainSelected: onTrainSelected,
currentInput: currentInput,
);
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return SelectTrainSuggestionsCupertino( return SelectTrainSuggestionsStateCupertino();
choices: choices,
onTrainSelected: onTrainSelected,
currentInput: currentInput,
);
case UiDesign.FLUENT:
return SelectTrainSuggestionsFluent(
choices: choices,
onTrainSelected: onTrainSelected,
currentInput: currentInput,
);
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
} }
} }
abstract class SelectTrainSuggestionsShared extends StatelessWidget { abstract class SelectTrainSuggestionsState extends State<SelectTrainSuggestions> {
String getUseCurrentInputWidgetText(String currentInput) => 'Caută trenul cu numărul $currentInput'; late String userInput;
Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected);
final List<TrainsResult> choices; List<TrainOperatorLines> operators = [];
final String? currentInput;
final void Function(String trainNumber) onTrainSelected; Future loadOperators(BuildContext context) async {
operators = [];
final operatorsString = await DefaultAssetBundle.of(context).loadString("assets/lines/files.txt");
final operatorsFilesList = operatorsString.split("\n");
final decoder = JsonDecoder();
for (final operatorFile in operatorsFilesList) {
final operatorString = await DefaultAssetBundle.of(context).loadString("assets/lines/$operatorFile");
final operatorData = decoder.convert(operatorString);
final _operator = TrainOperatorLines.fromJson(operatorData);
operators.add(_operator);
}
}
@override
void initState() {
super.initState();
userInput = widget.userInput;
loadOperators(context).then((_) {
setState(() {});
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (userInput != widget.userInput) {
setState(() {
userInput = widget.userInput;
});
}
}
const SelectTrainSuggestionsShared({ String getUseCurrentInputWidgetText(String currentInput) => 'Caută trenul cu numărul $currentInput';
required this.choices, Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected);
this.currentInput,
required this.onTrainSelected,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var slivers = choices.map((c) => c.company).toSet().map((operator) => OperatorAutocompleteSliver( var sliversTuple = operators.map(
operatorName: operator, (op) => Tuple2(
trains: choices.where((c) => c.company == operator).toList(), getFilteredLines(op, userInput),
onTrainSelected: onTrainSelected, op.operator,
)
).where((tuple) => tuple.item1.isNotEmpty).toList();
if (userInput.isNotEmpty) sliversTuple.sort((a, b) {
final aTrain = a.item1.first;
final bTrain = b.item1.first;
final inputAsRegExp = RegExp(userInput);
final matchOnA = inputAsRegExp.firstMatch(aTrain.number)!;
final matchOnB = inputAsRegExp.firstMatch(bTrain.number)!;
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start;
if (aTrain.number.length != bTrain.number.length) return aTrain.number.length - bTrain.number.length;
return aTrain.number.compareTo(bTrain.number);
});
var slivers = sliversTuple.map((tuple) => OperatorAutocompleteSliver(
uiDesign: widget.uiDesign,
operatorName: tuple.item2,
trains: tuple.item1,
onTrainSelected: widget.onTrainSelected,
)).toList(); )).toList();
return CustomScrollView( return CustomScrollView(
slivers: <Widget>[ slivers: <Widget>[
...slivers, ...slivers,
SliverToBoxAdapter( SliverToBoxAdapter(
child: currentInput != null && int.tryParse(currentInput!) != null ? getUseCurrentInputWidget(currentInput!, onTrainSelected) : Container(), child: int.tryParse(userInput) != null ? getUseCurrentInputWidget(userInput, widget.onTrainSelected) : Container(),
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Container( child: Container(
@ -79,21 +117,44 @@ abstract class SelectTrainSuggestionsShared extends StatelessWidget {
], ],
); );
} }
List<TrainOperatorTrainDescription> getFilteredLines(TrainOperatorLines _operator, String currentInput) {
if (currentInput.isNotEmpty) {
final filteredLines = _operator.trains
.where((elem) => elem.number.contains(currentInput))
.toList();
filteredLines.sort((a, b) {
final inputAsRegExp = RegExp(currentInput);
final matchOnA = inputAsRegExp.firstMatch(a.number)!;
final matchOnB = inputAsRegExp.firstMatch(b.number)!;
if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start;
if (a.number.length != b.number.length) return a.number.length - b.number.length;
return a.number.compareTo(b.number);
});
return filteredLines;
}
else {
return _operator.trains;
}
}
} }
class OperatorAutocompleteSliver extends ConsumerWidget { class OperatorAutocompleteSliver extends StatelessWidget {
final UiDesign? uiDesign;
final String operatorName; final String operatorName;
final List<TrainsResult> trains; final List<TrainOperatorTrainDescription> trains;
final void Function(String) onTrainSelected; final void Function(String) onTrainSelected;
const OperatorAutocompleteSliver({ const OperatorAutocompleteSliver({ Key? key, required this.uiDesign, required this.operatorName, required this.trains, required this.onTrainSelected }) : super(key: key);
super.key,
required this.operatorName,
required this.trains,
required this.onTrainSelected,
});
Widget mapTrainToItem(TrainsResult train, UiDesign uiDesign) { Widget mapTrainToItem(TrainOperatorTrainDescription train) {
final uiDesign = this.uiDesign ?? defaultUiDesign;
switch (uiDesign) { switch (uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
return OperatorAutocompleteTileMaterial( return OperatorAutocompleteTileMaterial(
@ -107,20 +168,13 @@ class OperatorAutocompleteSliver extends ConsumerWidget {
operatorName: operatorName, operatorName: operatorName,
train: train, train: train,
); );
case UiDesign.FLUENT:
return OperatorAutocompleteTileFluent(
onTrainSelected: onTrainSelected,
operatorName: operatorName,
train: train,
);
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
} }
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context) {
final uiDesign = ref.watch(uiDesignProvider);
if (trains.isEmpty) { if (trains.isEmpty) {
return SliverToBoxAdapter(child: Container(),); return SliverToBoxAdapter(child: Container(),);
} }
@ -128,14 +182,14 @@ class OperatorAutocompleteSliver extends ConsumerWidget {
return SliverPrototypeExtentList( return SliverPrototypeExtentList(
prototypeItem: Column( prototypeItem: Column(
children: <Widget>[ children: <Widget>[
mapTrainToItem(const TrainsResult(company: 'Company', number: '123', rank: 'R'), uiDesign), mapTrainToItem(TrainOperatorTrainDescription()),
], ],
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return Column( return Column(
children: <Widget>[ children: <Widget>[
mapTrainToItem(trains[index], uiDesign), mapTrainToItem(trains[index]),
], ],
); );
}, },
@ -148,7 +202,7 @@ class OperatorAutocompleteSliver extends ConsumerWidget {
abstract class OperatorAutocompleteTile extends StatelessWidget { abstract class OperatorAutocompleteTile extends StatelessWidget {
final String operatorName; final String operatorName;
final TrainsResult train; final TrainOperatorTrainDescription train;
final void Function(String) onTrainSelected; final void Function(String) onTrainSelected;
const OperatorAutocompleteTile({ Key? key, required this.onTrainSelected, required this.operatorName, required this.train }) : super(key: key); const OperatorAutocompleteTile({ Key? key, required this.onTrainSelected, required this.operatorName, required this.train }) : super(key: key);

24
lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart

@ -1,17 +1,9 @@
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/train_operator_lines.dart';
import 'package:info_tren/models.dart';
class SelectTrainSuggestionsCupertino extends SelectTrainSuggestionsShared {
const SelectTrainSuggestionsCupertino({
super.key,
required super.choices,
required super.onTrainSelected,
super.currentInput,
});
class SelectTrainSuggestionsStateCupertino extends SelectTrainSuggestionsState {
@override @override
Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected) { Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected) {
return Column( return Column(
@ -29,18 +21,18 @@ class SelectTrainSuggestionsCupertino extends SelectTrainSuggestionsShared {
], ],
) )
), ),
const CupertinoDivider(), CupertinoDivider(),
], ],
); );
} }
} }
class OperatorAutocompleteTileCupertino extends OperatorAutocompleteTile { class OperatorAutocompleteTileCupertino extends OperatorAutocompleteTile {
const OperatorAutocompleteTileCupertino({ OperatorAutocompleteTileCupertino({
Key? key, Key? key,
required String operatorName, required String operatorName,
required void Function(String) onTrainSelected, required void Function(String) onTrainSelected,
required TrainsResult train required TrainOperatorTrainDescription train
}): super( }): super(
onTrainSelected: onTrainSelected, onTrainSelected: onTrainSelected,
operatorName: operatorName, operatorName: operatorName,
@ -70,8 +62,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.rich( Text(
trainIdSpan(rank: train.rank, number: train.number), "${train.rang} ${train.number}",
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
], ],
@ -79,7 +71,7 @@ class OperatorAutocompleteTileCupertino extends OperatorAutocompleteTile {
), ),
), ),
), ),
const CupertinoDivider(), CupertinoDivider(),
], ],
); );
} }

85
lib/components/select_train_suggestions/select_train_suggestions_fluent.dart

@ -1,85 +0,0 @@
import 'package:fluent_ui/fluent_ui.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';
class SelectTrainSuggestionsFluent extends SelectTrainSuggestionsShared {
const SelectTrainSuggestionsFluent({
super.key,
required super.choices,
required super.onTrainSelected,
super.currentInput,
});
@override
Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Button(
child: Text(getUseCurrentInputWidgetText(currentInput)),
onPressed: () => onTrainSelected(currentInput),
),
],
)
),
const Divider(),
],
);
}
}
class OperatorAutocompleteTileFluent extends OperatorAutocompleteTile {
const OperatorAutocompleteTileFluent({
Key? key,
required String operatorName,
required void Function(String) onTrainSelected,
required TrainsResult train
}): super(
onTrainSelected: onTrainSelected,
operatorName: operatorName,
train: train,
key: key,
);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () {
onTrainSelected(train.number);
},
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 4),
child: SizedBox(
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
operatorName,
style: FluentTheme.of(context).typography.body?.copyWith(fontSize: 10, fontWeight: FontWeight.w200),
textAlign: TextAlign.left,
),
Text.rich(
trainIdSpan(rank: train.rank, number: train.number),
textAlign: TextAlign.left,
),
],
),
),
),
),
const Divider(),
],
);
}
}

20
lib/components/select_train_suggestions/select_train_suggestions_material.dart

@ -1,16 +1,8 @@
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/train_operator_lines.dart';
import 'package:info_tren/models.dart';
class SelectTrainSuggestionsMaterial extends SelectTrainSuggestionsShared {
const SelectTrainSuggestionsMaterial({
super.key,
required super.choices,
required super.onTrainSelected,
super.currentInput,
});
class SelectTrainSuggestionsStateMaterial extends SelectTrainSuggestionsState {
@override @override
Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected) { Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected) {
return Column( return Column(
@ -22,18 +14,18 @@ class SelectTrainSuggestionsMaterial extends SelectTrainSuggestionsShared {
onTrainSelected(currentInput); onTrainSelected(currentInput);
}, },
), ),
const Divider(), Divider(),
], ],
); );
} }
} }
class OperatorAutocompleteTileMaterial extends OperatorAutocompleteTile { class OperatorAutocompleteTileMaterial extends OperatorAutocompleteTile {
const OperatorAutocompleteTileMaterial({ OperatorAutocompleteTileMaterial({
Key? key, Key? key,
required String operatorName, required String operatorName,
required void Function(String) onTrainSelected, required void Function(String) onTrainSelected,
required TrainsResult train required TrainOperatorTrainDescription train
}): super( }): super(
onTrainSelected: onTrainSelected, onTrainSelected: onTrainSelected,
operatorName: operatorName, operatorName: operatorName,
@ -45,7 +37,7 @@ class OperatorAutocompleteTileMaterial extends OperatorAutocompleteTile {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
dense: true, dense: true,
title: Text.rich(trainIdSpan(rank: train.rank, number: train.number)), title: Text("${train.rang} ${train.number}"),
subtitle: Text(operatorName), subtitle: Text(operatorName),
onTap: () { onTap: () {
onTrainSelected(train.number); onTrainSelected(train.number);

13
lib/components/slim_app_bar.dart

@ -5,11 +5,10 @@ class SlimAppBar extends StatelessWidget {
final double size; final double size;
// final Function onBackTap; // final Function onBackTap;
const SlimAppBar({ SlimAppBar({
required this.title, required this.title,
this.size = 24, this.size = 24,
// this.onBackTap, // this.onBackTap,
super.key,
}); });
@override @override
@ -29,11 +28,11 @@ class SlimAppBar extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
SizedBox( Container(
height: size, height: size,
width: size, width: size,
child: (ModalRoute.of(context)?.canPop ?? false) child: (ModalRoute.of(context)?.canPop ?? false)
? const BackButtonIcon() ? BackButtonIcon()
: null, : null,
), ),
Expanded( Expanded(
@ -44,13 +43,13 @@ class SlimAppBar extends StatelessWidget {
title, title,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: style:
Theme.of(context).textTheme.titleSmall?.copyWith(color: Theme.of(context).textTheme.titleLarge?.color) ?? Theme.of(context).appBarTheme.textTheme?.caption?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.bodyText2?.color) ??
Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).textTheme.bodyMedium?.color), Theme.of(context).textTheme.caption?.copyWith(color: Theme.of(context).textTheme.bodyText2?.color),
), ),
), ),
), ),
), ),
SizedBox( Container(
height: size, height: size,
width: size, width: size,
), ),

2
lib/components/sliver_persistent_header_padding.dart

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class SliverPersistentHeaderPadding extends StatelessWidget { class SliverPersistentHeaderPadding extends StatelessWidget {
final double maxHeight; final double maxHeight;
const SliverPersistentHeaderPadding({required this.maxHeight, super.key,}); const SliverPersistentHeaderPadding({required this.maxHeight});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

39
lib/components/train_id_text_span.dart

@ -1,39 +0,0 @@
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,
);

211
lib/main.dart

@ -1,190 +1,91 @@
import 'dart:io'; import 'dart:io' show Platform;
import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/cupertino.dart';
import 'package:fluent_ui/fluent_ui.dart' as f; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart' as c;
import 'package:flutter/gestures.dart';
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_redux/flutter_redux.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/models.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:shared_preferences/shared_preferences.dart';
import 'package:timezone/data/latest.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
initializeTimeZones(); void main() {
final sharedPreferences = await SharedPreferences.getInstance(); // final store = createStore();
// runApp(
// StoreProvider(
// store: store,
// child: StartPoint(),
// )
// );
runApp( runApp(
ProviderScope( StartPoint(),
overrides: [
sharedPreferencesProvider.overrideWithValue(sharedPreferences),
],
child: const StartPoint(),
),
); );
} }
Map<String, WidgetBuilder> get routes => { Map<String, WidgetBuilder> routesByUiDesign(UiDesign uiDesign) => {
Navigator.defaultRouteName: (context) { Navigator.defaultRouteName: (context) {
return const MainPage(); return MainPage(uiDesign: uiDesign,);
},
AboutPage.routeName: (context) {
return const AboutPage();
},
SettingsPage.routeName: (context) {
return const SettingsPage();
}, },
SelectTrainPage.routeName: (context) { SelectTrainPage.routeName: (context) {
return const SelectTrainPage(); return SelectTrainPage(uiDesign: uiDesign);
}, },
TrainInfo.routeName: (context) { TrainInfo.routeName: (context) {
final args = ModalRoute.of(context)!.settings.arguments as TrainInfoArguments; return TrainInfo(
return ProviderScope( trainNumber: ModalRoute.of(context)!.settings.arguments as String,
overrides: [ uiDesign: uiDesign,
trainInfoArgumentsProvider.overrideWithValue(args),
],
child: const TrainInfo(),
); );
}, },
SelectStationPage.routeName: (context) { SelectStationPage.routeName: (context) {
return const SelectStationPage(); return SelectStationPage(uiDesign: uiDesign,);
}, },
ViewStationPage.routeName: (context) { ViewStationPage.routeName: (context) {
final args = ModalRoute.of(context)!.settings.arguments as ViewStationArguments; return ViewStationPage(
return ProviderScope( stationName: ModalRoute.of(context)!.settings.arguments as String,
overrides: [ uiDesign: uiDesign,
viewStationArgumentsProvider.overrideWithValue(args),
],
child: const ViewStationPage(),
); );
}, },
}; };
class DragFluentScrollBevahior extends f.FluentScrollBehavior { class StartPoint extends StatelessWidget {
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 {
final String appTitle = 'Info Tren'; final String appTitle = 'Info Tren';
const StartPoint({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context) {
final uiDesign = ref.watch(uiDesignProvider); if (Platform.isIOS || Platform.isMacOS) {
if (uiDesign == UiDesign.CUPERTINO) { return AnnotatedRegion(
return DynamicColorBuilder( value: const SystemUiOverlayStyle(
builder: (lightScheme, darkScheme) { statusBarBrightness: Brightness.dark,
return AnnotatedRegion( ),
value: const SystemUiOverlayStyle( child: CupertinoApp(
statusBarBrightness: c.Brightness.dark, title: appTitle,
), theme: CupertinoThemeData(
child: c.CupertinoApp( primaryColor: Colors.blue.shade600,
title: appTitle, brightness: 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: routesByUiDesign(UiDesign.CUPERTINO),
// ), ),
),
scrollBehavior: Platform.isLinux ? const DragCupertinoScrollBevahior() : null,
routes: routes,
),
);
}
);
}
else if (uiDesign == UiDesign.FLUENT) {
return DynamicColorBuilder(
builder: (lightScheme, darkScheme) {
return f.FluentApp(
title: appTitle,
theme: f.FluentThemeData(
brightness: f.Brightness.light,
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 DynamicColorBuilder( return MaterialApp(
builder: (lightScheme, darkScheme) { title: appTitle,
lightScheme ??= m.ColorScheme.fromSwatch( theme: ThemeData(
brightness: m.Brightness.light, primarySwatch: Colors.blue,
primarySwatch: m.Colors.blue, brightness: Brightness.dark,
); primaryColor: Colors.blue.shade600,
darkScheme ??= m.ColorScheme.fromSwatch( accentColor: Colors.blue.shade700,
brightness: m.Brightness.dark, // fontFamily: 'Atkinson Hyperlegible',
primarySwatch: m.Colors.blue, ),
); routes: routesByUiDesign(UiDesign.MATERIAL),
return m.MaterialApp(
title: appTitle,
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,
);
}
); );
} }
} }

14
lib/models.dart

@ -1,14 +0,0 @@
export 'package:info_tren/models/changelog_entry.dart';
export 'package:info_tren/models/station_arrdep.dart';
export 'package:info_tren/models/station_data.dart';
export 'package:info_tren/models/station_status.dart';
export 'package:info_tren/models/station_train.dart';
export 'package:info_tren/models/stations_result.dart';
export 'package:info_tren/models/train_data.dart';
export 'package:info_tren/models/trains_result.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 TrainDataStatusState;
typedef TrainDataState = TrainDataStatusState;

83
lib/models/changelog_entry.dart

@ -1,83 +0,0 @@
import 'package:quiver/core.dart';
class ChangelogEntry {
final ChangelogVersion version;
final String description;
final Uri? apkLink;
final Uri? linuxLink;
final Uri? windowsLink;
ChangelogEntry({
required this.version,
required this.description,
this.apkLink,
this.linuxLink,
this.windowsLink,
});
factory ChangelogEntry.fromTextBlock(String text) {
final lines = text.split(RegExp(r'(\r?\n)+'));
return ChangelogEntry(
version: ChangelogVersion.parse(lines.first),
description: lines.skip(1).join('\n'),
);
}
static List<ChangelogEntry> fromTextFile(String text) {
final blocks = text.split(RegExp(r'(\r?\n){2}'));
return blocks.map(ChangelogEntry.fromTextBlock).toList();
}
}
class ChangelogVersion implements Comparable<ChangelogVersion> {
final int major;
final int minor;
final int patch;
final String? prerelease;
ChangelogVersion(this.major, this.minor, this.patch, [this.prerelease]);
factory ChangelogVersion.parse(String version) {
if (version.startsWith('v')) {
version = version.substring(1);
}
String? prerelease;
if (version.contains('-')) {
final index = version.indexOf('-');
prerelease = version.substring(index + 1);
version = version.substring(0, index);
}
final splitted = version.split('.').map(int.parse).toList();
return ChangelogVersion(splitted[0], splitted[1], splitted[2], prerelease);
}
@override
String toString() {
final vString = 'v$major.$minor.$patch';
return prerelease == null ? vString : '$vString-$prerelease';
}
@override
bool operator==(dynamic other) {
if (other is! ChangelogVersion) {
return false;
}
return major == other.major && minor == other.minor && patch == other.patch && prerelease == other.prerelease;
}
@override
int get hashCode {
return hash3(major.hashCode, minor.hashCode, patch.hashCode);
}
@override
int compareTo(ChangelogVersion other) {
if (major != other.major) {
return major.compareTo(other.major);
}
if (minor != other.minor) {
return minor.compareTo(other.minor);
}
return patch.compareTo(other.patch);
}
}

18
lib/models/station_arrdep.dart

@ -1,18 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:info_tren/models.dart';
part 'station_arrdep.g.dart';
part 'station_arrdep.freezed.dart';
@freezed
class StationArrDep with _$StationArrDep {
const factory StationArrDep({
required int? stoppingTime,
required DateTime time,
required StationTrain train,
required StationStatus status,
}) = _StationArrDep;
factory StationArrDep.fromJson(Map<String, dynamic> json) => _$StationArrDepFromJson(json);
}

241
lib/models/station_arrdep.freezed.dart

@ -1,241 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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, unnecessary_question_mark
part of 'station_arrdep.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
StationArrDep _$StationArrDepFromJson(Map<String, dynamic> json) {
return _StationArrDep.fromJson(json);
}
/// @nodoc
mixin _$StationArrDep {
int? get stoppingTime => throw _privateConstructorUsedError;
DateTime get time => throw _privateConstructorUsedError;
StationTrain get train => throw _privateConstructorUsedError;
StationStatus get status => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$StationArrDepCopyWith<StationArrDep> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $StationArrDepCopyWith<$Res> {
factory $StationArrDepCopyWith(
StationArrDep value, $Res Function(StationArrDep) then) =
_$StationArrDepCopyWithImpl<$Res, StationArrDep>;
@useResult
$Res call(
{int? stoppingTime,
DateTime time,
StationTrain train,
StationStatus status});
$StationTrainCopyWith<$Res> get train;
$StationStatusCopyWith<$Res> get status;
}
/// @nodoc
class _$StationArrDepCopyWithImpl<$Res, $Val extends StationArrDep>
implements $StationArrDepCopyWith<$Res> {
_$StationArrDepCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? stoppingTime = freezed,
Object? time = null,
Object? train = null,
Object? status = null,
}) {
return _then(_value.copyWith(
stoppingTime: freezed == stoppingTime
? _value.stoppingTime
: stoppingTime // ignore: cast_nullable_to_non_nullable
as int?,
time: null == time
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as DateTime,
train: null == train
? _value.train
: train // ignore: cast_nullable_to_non_nullable
as StationTrain,
status: null == status
? _value.status
: status // ignore: cast_nullable_to_non_nullable
as StationStatus,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$StationTrainCopyWith<$Res> get train {
return $StationTrainCopyWith<$Res>(_value.train, (value) {
return _then(_value.copyWith(train: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$StationStatusCopyWith<$Res> get status {
return $StationStatusCopyWith<$Res>(_value.status, (value) {
return _then(_value.copyWith(status: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$_StationArrDepCopyWith<$Res>
implements $StationArrDepCopyWith<$Res> {
factory _$$_StationArrDepCopyWith(
_$_StationArrDep value, $Res Function(_$_StationArrDep) then) =
__$$_StationArrDepCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int? stoppingTime,
DateTime time,
StationTrain train,
StationStatus status});
@override
$StationTrainCopyWith<$Res> get train;
@override
$StationStatusCopyWith<$Res> get status;
}
/// @nodoc
class __$$_StationArrDepCopyWithImpl<$Res>
extends _$StationArrDepCopyWithImpl<$Res, _$_StationArrDep>
implements _$$_StationArrDepCopyWith<$Res> {
__$$_StationArrDepCopyWithImpl(
_$_StationArrDep _value, $Res Function(_$_StationArrDep) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? stoppingTime = freezed,
Object? time = null,
Object? train = null,
Object? status = null,
}) {
return _then(_$_StationArrDep(
stoppingTime: freezed == stoppingTime
? _value.stoppingTime
: stoppingTime // ignore: cast_nullable_to_non_nullable
as int?,
time: null == time
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as DateTime,
train: null == train
? _value.train
: train // ignore: cast_nullable_to_non_nullable
as StationTrain,
status: null == status
? _value.status
: status // ignore: cast_nullable_to_non_nullable
as StationStatus,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_StationArrDep implements _StationArrDep {
const _$_StationArrDep(
{required this.stoppingTime,
required this.time,
required this.train,
required this.status});
factory _$_StationArrDep.fromJson(Map<String, dynamic> json) =>
_$$_StationArrDepFromJson(json);
@override
final int? stoppingTime;
@override
final DateTime time;
@override
final StationTrain train;
@override
final StationStatus status;
@override
String toString() {
return 'StationArrDep(stoppingTime: $stoppingTime, time: $time, train: $train, status: $status)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_StationArrDep &&
(identical(other.stoppingTime, stoppingTime) ||
other.stoppingTime == stoppingTime) &&
(identical(other.time, time) || other.time == time) &&
(identical(other.train, train) || other.train == train) &&
(identical(other.status, status) || other.status == status));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, stoppingTime, time, train, status);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_StationArrDepCopyWith<_$_StationArrDep> get copyWith =>
__$$_StationArrDepCopyWithImpl<_$_StationArrDep>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_StationArrDepToJson(
this,
);
}
}
abstract class _StationArrDep implements StationArrDep {
const factory _StationArrDep(
{required final int? stoppingTime,
required final DateTime time,
required final StationTrain train,
required final StationStatus status}) = _$_StationArrDep;
factory _StationArrDep.fromJson(Map<String, dynamic> json) =
_$_StationArrDep.fromJson;
@override
int? get stoppingTime;
@override
DateTime get time;
@override
StationTrain get train;
@override
StationStatus get status;
@override
@JsonKey(ignore: true)
_$$_StationArrDepCopyWith<_$_StationArrDep> get copyWith =>
throw _privateConstructorUsedError;
}

23
lib/models/station_arrdep.g.dart

@ -1,23 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'station_arrdep.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_StationArrDep _$$_StationArrDepFromJson(Map<String, dynamic> json) =>
_$_StationArrDep(
stoppingTime: json['stoppingTime'] as int?,
time: DateTime.parse(json['time'] as String),
train: StationTrain.fromJson(json['train'] as Map<String, dynamic>),
status: StationStatus.fromJson(json['status'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$_StationArrDepToJson(_$_StationArrDep instance) =>
<String, dynamic>{
'stoppingTime': instance.stoppingTime,
'time': instance.time.toIso8601String(),
'train': instance.train,
'status': instance.status,
};

73
lib/models/station_data.dart

@ -1,17 +1,68 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:info_tren/models.dart';
part 'station_data.g.dart'; part 'station_data.g.dart';
part 'station_data.freezed.dart';
@freezed @JsonSerializable()
class StationData with _$StationData { class StationData {
const factory StationData({ final String date;
required String date, final String stationName;
required String stationName, final List<StationArrival>? arrivals;
required List<StationArrDep>? arrivals, final List<StationDeparture>? departures;
required List<StationArrDep>? departures,
}) = _StationData; const StationData({required this.date, required this.stationName, required this.arrivals, required this.departures});
factory StationData.fromJson(Map<String, dynamic> json) => _$StationDataFromJson(json); factory StationData.fromJson(Map<String, dynamic> json) => _$StationDataFromJson(json);
Map<String, dynamic> toJson() => _$StationDataToJson(this);
}
@JsonSerializable()
class StationArrival {
final int? stoppingTime;
final DateTime time;
final StationTrainArr train;
const StationArrival({required this.stoppingTime, required this.time, required this.train,});
factory StationArrival.fromJson(Map<String, dynamic> json) => _$StationArrivalFromJson(json);
Map<String, dynamic> toJson() => _$StationArrivalToJson(this);
}
@JsonSerializable()
class StationDeparture {
final int? stoppingTime;
final DateTime time;
final StationTrainDep train;
const StationDeparture({required this.stoppingTime, required this.time, required this.train,});
factory StationDeparture.fromJson(Map<String, dynamic> json) => _$StationDepartureFromJson(json);
Map<String, dynamic> toJson() => _$StationDepartureToJson(this);
}
@JsonSerializable()
class StationTrainArr {
final String rank;
final String number;
final String operator;
final String origin;
final List<String>? route;
StationTrainArr({required this.rank, required this.number, required this.operator, required this.origin, this.route,});
factory StationTrainArr.fromJson(Map<String, dynamic> json) => _$StationTrainArrFromJson(json);
Map<String, dynamic> toJson() => _$StationTrainArrToJson(this);
}
@JsonSerializable()
class StationTrainDep {
final String rank;
final String number;
final String operator;
final String destination;
final List<String>? route;
StationTrainDep({required this.rank, required this.number, required this.operator, required this.destination, this.route,});
factory StationTrainDep.fromJson(Map<String, dynamic> json) => _$StationTrainDepFromJson(json);
Map<String, dynamic> toJson() => _$StationTrainDepToJson(this);
} }

239
lib/models/station_data.freezed.dart

@ -1,239 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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, unnecessary_question_mark
part of 'station_data.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
StationData _$StationDataFromJson(Map<String, dynamic> json) {
return _StationData.fromJson(json);
}
/// @nodoc
mixin _$StationData {
String get date => throw _privateConstructorUsedError;
String get stationName => throw _privateConstructorUsedError;
List<StationArrDep>? get arrivals => throw _privateConstructorUsedError;
List<StationArrDep>? get departures => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$StationDataCopyWith<StationData> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $StationDataCopyWith<$Res> {
factory $StationDataCopyWith(
StationData value, $Res Function(StationData) then) =
_$StationDataCopyWithImpl<$Res, StationData>;
@useResult
$Res call(
{String date,
String stationName,
List<StationArrDep>? arrivals,
List<StationArrDep>? departures});
}
/// @nodoc
class _$StationDataCopyWithImpl<$Res, $Val extends StationData>
implements $StationDataCopyWith<$Res> {
_$StationDataCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? date = null,
Object? stationName = null,
Object? arrivals = freezed,
Object? departures = freezed,
}) {
return _then(_value.copyWith(
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as String,
stationName: null == stationName
? _value.stationName
: stationName // ignore: cast_nullable_to_non_nullable
as String,
arrivals: freezed == arrivals
? _value.arrivals
: arrivals // ignore: cast_nullable_to_non_nullable
as List<StationArrDep>?,
departures: freezed == departures
? _value.departures
: departures // ignore: cast_nullable_to_non_nullable
as List<StationArrDep>?,
) as $Val);
}
}
/// @nodoc
abstract class _$$_StationDataCopyWith<$Res>
implements $StationDataCopyWith<$Res> {
factory _$$_StationDataCopyWith(
_$_StationData value, $Res Function(_$_StationData) then) =
__$$_StationDataCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String date,
String stationName,
List<StationArrDep>? arrivals,
List<StationArrDep>? departures});
}
/// @nodoc
class __$$_StationDataCopyWithImpl<$Res>
extends _$StationDataCopyWithImpl<$Res, _$_StationData>
implements _$$_StationDataCopyWith<$Res> {
__$$_StationDataCopyWithImpl(
_$_StationData _value, $Res Function(_$_StationData) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? date = null,
Object? stationName = null,
Object? arrivals = freezed,
Object? departures = freezed,
}) {
return _then(_$_StationData(
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as String,
stationName: null == stationName
? _value.stationName
: stationName // ignore: cast_nullable_to_non_nullable
as String,
arrivals: freezed == arrivals
? _value._arrivals
: arrivals // ignore: cast_nullable_to_non_nullable
as List<StationArrDep>?,
departures: freezed == departures
? _value._departures
: departures // ignore: cast_nullable_to_non_nullable
as List<StationArrDep>?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_StationData implements _StationData {
const _$_StationData(
{required this.date,
required this.stationName,
required final List<StationArrDep>? arrivals,
required final List<StationArrDep>? departures})
: _arrivals = arrivals,
_departures = departures;
factory _$_StationData.fromJson(Map<String, dynamic> json) =>
_$$_StationDataFromJson(json);
@override
final String date;
@override
final String stationName;
final List<StationArrDep>? _arrivals;
@override
List<StationArrDep>? get arrivals {
final value = _arrivals;
if (value == null) return null;
if (_arrivals is EqualUnmodifiableListView) return _arrivals;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
final List<StationArrDep>? _departures;
@override
List<StationArrDep>? get departures {
final value = _departures;
if (value == null) return null;
if (_departures is EqualUnmodifiableListView) return _departures;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString() {
return 'StationData(date: $date, stationName: $stationName, arrivals: $arrivals, departures: $departures)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_StationData &&
(identical(other.date, date) || other.date == date) &&
(identical(other.stationName, stationName) ||
other.stationName == stationName) &&
const DeepCollectionEquality().equals(other._arrivals, _arrivals) &&
const DeepCollectionEquality()
.equals(other._departures, _departures));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
date,
stationName,
const DeepCollectionEquality().hash(_arrivals),
const DeepCollectionEquality().hash(_departures));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_StationDataCopyWith<_$_StationData> get copyWith =>
__$$_StationDataCopyWithImpl<_$_StationData>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_StationDataToJson(
this,
);
}
}
abstract class _StationData implements StationData {
const factory _StationData(
{required final String date,
required final String stationName,
required final List<StationArrDep>? arrivals,
required final List<StationArrDep>? departures}) = _$_StationData;
factory _StationData.fromJson(Map<String, dynamic> json) =
_$_StationData.fromJson;
@override
String get date;
@override
String get stationName;
@override
List<StationArrDep>? get arrivals;
@override
List<StationArrDep>? get departures;
@override
@JsonKey(ignore: true)
_$$_StationDataCopyWith<_$_StationData> get copyWith =>
throw _privateConstructorUsedError;
}

75
lib/models/station_data.g.dart

@ -6,22 +6,87 @@ part of 'station_data.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$_StationData _$$_StationDataFromJson(Map<String, dynamic> json) => StationData _$StationDataFromJson(Map<String, dynamic> json) => StationData(
_$_StationData(
date: json['date'] as String, date: json['date'] as String,
stationName: json['stationName'] as String, stationName: json['stationName'] as String,
arrivals: (json['arrivals'] as List<dynamic>?) arrivals: (json['arrivals'] as List<dynamic>?)
?.map((e) => StationArrDep.fromJson(e as Map<String, dynamic>)) ?.map((e) => StationArrival.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
departures: (json['departures'] as List<dynamic>?) departures: (json['departures'] as List<dynamic>?)
?.map((e) => StationArrDep.fromJson(e as Map<String, dynamic>)) ?.map((e) => StationDeparture.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
); );
Map<String, dynamic> _$$_StationDataToJson(_$_StationData instance) => Map<String, dynamic> _$StationDataToJson(StationData instance) =>
<String, dynamic>{ <String, dynamic>{
'date': instance.date, 'date': instance.date,
'stationName': instance.stationName, 'stationName': instance.stationName,
'arrivals': instance.arrivals, 'arrivals': instance.arrivals,
'departures': instance.departures, 'departures': instance.departures,
}; };
StationArrival _$StationArrivalFromJson(Map<String, dynamic> json) =>
StationArrival(
stoppingTime: json['stoppingTime'] as int?,
time: DateTime.parse(json['time'] as String),
train: StationTrainArr.fromJson(json['train'] as Map<String, dynamic>),
);
Map<String, dynamic> _$StationArrivalToJson(StationArrival instance) =>
<String, dynamic>{
'stoppingTime': instance.stoppingTime,
'time': instance.time.toIso8601String(),
'train': instance.train,
};
StationDeparture _$StationDepartureFromJson(Map<String, dynamic> json) =>
StationDeparture(
stoppingTime: json['stoppingTime'] as int?,
time: DateTime.parse(json['time'] as String),
train: StationTrainDep.fromJson(json['train'] as Map<String, dynamic>),
);
Map<String, dynamic> _$StationDepartureToJson(StationDeparture instance) =>
<String, dynamic>{
'stoppingTime': instance.stoppingTime,
'time': instance.time.toIso8601String(),
'train': instance.train,
};
StationTrainArr _$StationTrainArrFromJson(Map<String, dynamic> json) =>
StationTrainArr(
rank: json['rank'] as String,
number: json['number'] as String,
operator: json['operator'] as String,
origin: json['origin'] as String,
route:
(json['route'] as List<dynamic>?)?.map((e) => e as String).toList(),
);
Map<String, dynamic> _$StationTrainArrToJson(StationTrainArr instance) =>
<String, dynamic>{
'rank': instance.rank,
'number': instance.number,
'operator': instance.operator,
'origin': instance.origin,
'route': instance.route,
};
StationTrainDep _$StationTrainDepFromJson(Map<String, dynamic> json) =>
StationTrainDep(
rank: json['rank'] as String,
number: json['number'] as String,
operator: json['operator'] as String,
destination: json['destination'] as String,
route:
(json['route'] as List<dynamic>?)?.map((e) => e as String).toList(),
);
Map<String, dynamic> _$StationTrainDepToJson(StationTrainDep instance) =>
<String, dynamic>{
'rank': instance.rank,
'number': instance.number,
'operator': instance.operator,
'destination': instance.destination,
'route': instance.route,
};

17
lib/models/station_status.dart

@ -1,17 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'station_status.g.dart';
part 'station_status.freezed.dart';
@freezed
class StationStatus with _$StationStatus {
const factory StationStatus({
required int delay,
required bool real,
required bool cancelled,
required String? platform,
}) = _StationStatus;
factory StationStatus.fromJson(Map<String, dynamic> json) => _$StationStatusFromJson(json);
}

210
lib/models/station_status.freezed.dart

@ -1,210 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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, unnecessary_question_mark
part of 'station_status.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
StationStatus _$StationStatusFromJson(Map<String, dynamic> json) {
return _StationStatus.fromJson(json);
}
/// @nodoc
mixin _$StationStatus {
int get delay => throw _privateConstructorUsedError;
bool get real => throw _privateConstructorUsedError;
bool get cancelled => throw _privateConstructorUsedError;
String? get platform => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$StationStatusCopyWith<StationStatus> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $StationStatusCopyWith<$Res> {
factory $StationStatusCopyWith(
StationStatus value, $Res Function(StationStatus) then) =
_$StationStatusCopyWithImpl<$Res, StationStatus>;
@useResult
$Res call({int delay, bool real, bool cancelled, String? platform});
}
/// @nodoc
class _$StationStatusCopyWithImpl<$Res, $Val extends StationStatus>
implements $StationStatusCopyWith<$Res> {
_$StationStatusCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? delay = null,
Object? real = null,
Object? cancelled = null,
Object? platform = freezed,
}) {
return _then(_value.copyWith(
delay: null == delay
? _value.delay
: delay // ignore: cast_nullable_to_non_nullable
as int,
real: null == real
? _value.real
: real // ignore: cast_nullable_to_non_nullable
as bool,
cancelled: null == cancelled
? _value.cancelled
: cancelled // ignore: cast_nullable_to_non_nullable
as bool,
platform: freezed == platform
? _value.platform
: platform // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$_StationStatusCopyWith<$Res>
implements $StationStatusCopyWith<$Res> {
factory _$$_StationStatusCopyWith(
_$_StationStatus value, $Res Function(_$_StationStatus) then) =
__$$_StationStatusCopyWithImpl<$Res>;
@override
@useResult
$Res call({int delay, bool real, bool cancelled, String? platform});
}
/// @nodoc
class __$$_StationStatusCopyWithImpl<$Res>
extends _$StationStatusCopyWithImpl<$Res, _$_StationStatus>
implements _$$_StationStatusCopyWith<$Res> {
__$$_StationStatusCopyWithImpl(
_$_StationStatus _value, $Res Function(_$_StationStatus) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? delay = null,
Object? real = null,
Object? cancelled = null,
Object? platform = freezed,
}) {
return _then(_$_StationStatus(
delay: null == delay
? _value.delay
: delay // ignore: cast_nullable_to_non_nullable
as int,
real: null == real
? _value.real
: real // ignore: cast_nullable_to_non_nullable
as bool,
cancelled: null == cancelled
? _value.cancelled
: cancelled // ignore: cast_nullable_to_non_nullable
as bool,
platform: freezed == platform
? _value.platform
: platform // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_StationStatus implements _StationStatus {
const _$_StationStatus(
{required this.delay,
required this.real,
required this.cancelled,
required this.platform});
factory _$_StationStatus.fromJson(Map<String, dynamic> json) =>
_$$_StationStatusFromJson(json);
@override
final int delay;
@override
final bool real;
@override
final bool cancelled;
@override
final String? platform;
@override
String toString() {
return 'StationStatus(delay: $delay, real: $real, cancelled: $cancelled, platform: $platform)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_StationStatus &&
(identical(other.delay, delay) || other.delay == delay) &&
(identical(other.real, real) || other.real == real) &&
(identical(other.cancelled, cancelled) ||
other.cancelled == cancelled) &&
(identical(other.platform, platform) ||
other.platform == platform));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, delay, real, cancelled, platform);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_StationStatusCopyWith<_$_StationStatus> get copyWith =>
__$$_StationStatusCopyWithImpl<_$_StationStatus>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_StationStatusToJson(
this,
);
}
}
abstract class _StationStatus implements StationStatus {
const factory _StationStatus(
{required final int delay,
required final bool real,
required final bool cancelled,
required final String? platform}) = _$_StationStatus;
factory _StationStatus.fromJson(Map<String, dynamic> json) =
_$_StationStatus.fromJson;
@override
int get delay;
@override
bool get real;
@override
bool get cancelled;
@override
String? get platform;
@override
@JsonKey(ignore: true)
_$$_StationStatusCopyWith<_$_StationStatus> get copyWith =>
throw _privateConstructorUsedError;
}

23
lib/models/station_status.g.dart

@ -1,23 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'station_status.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_StationStatus _$$_StationStatusFromJson(Map<String, dynamic> json) =>
_$_StationStatus(
delay: json['delay'] as int,
real: json['real'] as bool,
cancelled: json['cancelled'] as bool,
platform: json['platform'] as String?,
);
Map<String, dynamic> _$$_StationStatusToJson(_$_StationStatus instance) =>
<String, dynamic>{
'delay': instance.delay,
'real': instance.real,
'cancelled': instance.cancelled,
'platform': instance.platform,
};

19
lib/models/station_train.dart

@ -1,19 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'station_train.g.dart';
part 'station_train.freezed.dart';
@freezed
class StationTrain with _$StationTrain {
const factory StationTrain({
required String rank,
required String number,
required String operator,
required String terminus,
List<String>? route,
required DateTime departureDate,
}) = _StationTrain;
factory StationTrain.fromJson(Map<String, dynamic> json) => _$StationTrainFromJson(json);
}

268
lib/models/station_train.freezed.dart

@ -1,268 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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, unnecessary_question_mark
part of 'station_train.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
StationTrain _$StationTrainFromJson(Map<String, dynamic> json) {
return _StationTrain.fromJson(json);
}
/// @nodoc
mixin _$StationTrain {
String get rank => throw _privateConstructorUsedError;
String get number => throw _privateConstructorUsedError;
String get operator => throw _privateConstructorUsedError;
String get terminus => throw _privateConstructorUsedError;
List<String>? get route => throw _privateConstructorUsedError;
DateTime get departureDate => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$StationTrainCopyWith<StationTrain> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $StationTrainCopyWith<$Res> {
factory $StationTrainCopyWith(
StationTrain value, $Res Function(StationTrain) then) =
_$StationTrainCopyWithImpl<$Res, StationTrain>;
@useResult
$Res call(
{String rank,
String number,
String operator,
String terminus,
List<String>? route,
DateTime departureDate});
}
/// @nodoc
class _$StationTrainCopyWithImpl<$Res, $Val extends StationTrain>
implements $StationTrainCopyWith<$Res> {
_$StationTrainCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? rank = null,
Object? number = null,
Object? operator = null,
Object? terminus = null,
Object? route = freezed,
Object? departureDate = null,
}) {
return _then(_value.copyWith(
rank: null == rank
? _value.rank
: rank // ignore: cast_nullable_to_non_nullable
as String,
number: null == number
? _value.number
: number // ignore: cast_nullable_to_non_nullable
as String,
operator: null == operator
? _value.operator
: operator // ignore: cast_nullable_to_non_nullable
as String,
terminus: null == terminus
? _value.terminus
: terminus // ignore: cast_nullable_to_non_nullable
as String,
route: freezed == route
? _value.route
: route // ignore: cast_nullable_to_non_nullable
as List<String>?,
departureDate: null == departureDate
? _value.departureDate
: departureDate // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$_StationTrainCopyWith<$Res>
implements $StationTrainCopyWith<$Res> {
factory _$$_StationTrainCopyWith(
_$_StationTrain value, $Res Function(_$_StationTrain) then) =
__$$_StationTrainCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String rank,
String number,
String operator,
String terminus,
List<String>? route,
DateTime departureDate});
}
/// @nodoc
class __$$_StationTrainCopyWithImpl<$Res>
extends _$StationTrainCopyWithImpl<$Res, _$_StationTrain>
implements _$$_StationTrainCopyWith<$Res> {
__$$_StationTrainCopyWithImpl(
_$_StationTrain _value, $Res Function(_$_StationTrain) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? rank = null,
Object? number = null,
Object? operator = null,
Object? terminus = null,
Object? route = freezed,
Object? departureDate = null,
}) {
return _then(_$_StationTrain(
rank: null == rank
? _value.rank
: rank // ignore: cast_nullable_to_non_nullable
as String,
number: null == number
? _value.number
: number // ignore: cast_nullable_to_non_nullable
as String,
operator: null == operator
? _value.operator
: operator // ignore: cast_nullable_to_non_nullable
as String,
terminus: null == terminus
? _value.terminus
: terminus // ignore: cast_nullable_to_non_nullable
as String,
route: freezed == route
? _value._route
: route // ignore: cast_nullable_to_non_nullable
as List<String>?,
departureDate: null == departureDate
? _value.departureDate
: departureDate // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_StationTrain implements _StationTrain {
const _$_StationTrain(
{required this.rank,
required this.number,
required this.operator,
required this.terminus,
final List<String>? route,
required this.departureDate})
: _route = route;
factory _$_StationTrain.fromJson(Map<String, dynamic> json) =>
_$$_StationTrainFromJson(json);
@override
final String rank;
@override
final String number;
@override
final String operator;
@override
final String terminus;
final List<String>? _route;
@override
List<String>? get route {
final value = _route;
if (value == null) return null;
if (_route is EqualUnmodifiableListView) return _route;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
final DateTime departureDate;
@override
String toString() {
return 'StationTrain(rank: $rank, number: $number, operator: $operator, terminus: $terminus, route: $route, departureDate: $departureDate)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_StationTrain &&
(identical(other.rank, rank) || other.rank == rank) &&
(identical(other.number, number) || other.number == number) &&
(identical(other.operator, operator) ||
other.operator == operator) &&
(identical(other.terminus, terminus) ||
other.terminus == terminus) &&
const DeepCollectionEquality().equals(other._route, _route) &&
(identical(other.departureDate, departureDate) ||
other.departureDate == departureDate));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, rank, number, operator, terminus,
const DeepCollectionEquality().hash(_route), departureDate);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_StationTrainCopyWith<_$_StationTrain> get copyWith =>
__$$_StationTrainCopyWithImpl<_$_StationTrain>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_StationTrainToJson(
this,
);
}
}
abstract class _StationTrain implements StationTrain {
const factory _StationTrain(
{required final String rank,
required final String number,
required final String operator,
required final String terminus,
final List<String>? route,
required final DateTime departureDate}) = _$_StationTrain;
factory _StationTrain.fromJson(Map<String, dynamic> json) =
_$_StationTrain.fromJson;
@override
String get rank;
@override
String get number;
@override
String get operator;
@override
String get terminus;
@override
List<String>? get route;
@override
DateTime get departureDate;
@override
@JsonKey(ignore: true)
_$$_StationTrainCopyWith<_$_StationTrain> get copyWith =>
throw _privateConstructorUsedError;
}

28
lib/models/station_train.g.dart

@ -1,28 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'station_train.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_StationTrain _$$_StationTrainFromJson(Map<String, dynamic> json) =>
_$_StationTrain(
rank: json['rank'] as String,
number: json['number'] as String,
operator: json['operator'] as String,
terminus: json['terminus'] as String,
route:
(json['route'] as List<dynamic>?)?.map((e) => e as String).toList(),
departureDate: DateTime.parse(json['departureDate'] as String),
);
Map<String, dynamic> _$$_StationTrainToJson(_$_StationTrain instance) =>
<String, dynamic>{
'rank': instance.rank,
'number': instance.number,
'operator': instance.operator,
'terminus': instance.terminus,
'route': instance.route,
'departureDate': instance.departureDate.toIso8601String(),
};

18
lib/models/stations_result.dart

@ -1,14 +1,14 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'stations_result.g.dart'; part 'stations_result.g.dart';
part 'stations_result.freezed.dart';
@freezed @JsonSerializable()
class StationsResult with _$StationsResult { class StationsResult {
const factory StationsResult({ final String name;
required String name, final List<String>? stoppedAtBy;
List<String>? stoppedAtBy,
}) = _StationsResult; const StationsResult({required this.name, this.stoppedAtBy});
factory StationsResult.fromJson(Map<String, dynamic> json) => _$StationsResultFromJson(json); factory StationsResult.fromJson(Map<String, dynamic> json) => _$StationsResultFromJson(json);
} Map<String, dynamic> toJson() => _$StationsResultToJson(this);
}

179
lib/models/stations_result.freezed.dart

@ -1,179 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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, unnecessary_question_mark
part of 'stations_result.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
StationsResult _$StationsResultFromJson(Map<String, dynamic> json) {
return _StationsResult.fromJson(json);
}
/// @nodoc
mixin _$StationsResult {
String get name => throw _privateConstructorUsedError;
List<String>? get stoppedAtBy => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$StationsResultCopyWith<StationsResult> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $StationsResultCopyWith<$Res> {
factory $StationsResultCopyWith(
StationsResult value, $Res Function(StationsResult) then) =
_$StationsResultCopyWithImpl<$Res, StationsResult>;
@useResult
$Res call({String name, List<String>? stoppedAtBy});
}
/// @nodoc
class _$StationsResultCopyWithImpl<$Res, $Val extends StationsResult>
implements $StationsResultCopyWith<$Res> {
_$StationsResultCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? stoppedAtBy = freezed,
}) {
return _then(_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
stoppedAtBy: freezed == stoppedAtBy
? _value.stoppedAtBy
: stoppedAtBy // ignore: cast_nullable_to_non_nullable
as List<String>?,
) as $Val);
}
}
/// @nodoc
abstract class _$$_StationsResultCopyWith<$Res>
implements $StationsResultCopyWith<$Res> {
factory _$$_StationsResultCopyWith(
_$_StationsResult value, $Res Function(_$_StationsResult) then) =
__$$_StationsResultCopyWithImpl<$Res>;
@override
@useResult
$Res call({String name, List<String>? stoppedAtBy});
}
/// @nodoc
class __$$_StationsResultCopyWithImpl<$Res>
extends _$StationsResultCopyWithImpl<$Res, _$_StationsResult>
implements _$$_StationsResultCopyWith<$Res> {
__$$_StationsResultCopyWithImpl(
_$_StationsResult _value, $Res Function(_$_StationsResult) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? stoppedAtBy = freezed,
}) {
return _then(_$_StationsResult(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
stoppedAtBy: freezed == stoppedAtBy
? _value._stoppedAtBy
: stoppedAtBy // ignore: cast_nullable_to_non_nullable
as List<String>?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_StationsResult implements _StationsResult {
const _$_StationsResult({required this.name, final List<String>? stoppedAtBy})
: _stoppedAtBy = stoppedAtBy;
factory _$_StationsResult.fromJson(Map<String, dynamic> json) =>
_$$_StationsResultFromJson(json);
@override
final String name;
final List<String>? _stoppedAtBy;
@override
List<String>? get stoppedAtBy {
final value = _stoppedAtBy;
if (value == null) return null;
if (_stoppedAtBy is EqualUnmodifiableListView) return _stoppedAtBy;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString() {
return 'StationsResult(name: $name, stoppedAtBy: $stoppedAtBy)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_StationsResult &&
(identical(other.name, name) || other.name == name) &&
const DeepCollectionEquality()
.equals(other._stoppedAtBy, _stoppedAtBy));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, name, const DeepCollectionEquality().hash(_stoppedAtBy));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_StationsResultCopyWith<_$_StationsResult> get copyWith =>
__$$_StationsResultCopyWithImpl<_$_StationsResult>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_StationsResultToJson(
this,
);
}
}
abstract class _StationsResult implements StationsResult {
const factory _StationsResult(
{required final String name,
final List<String>? stoppedAtBy}) = _$_StationsResult;
factory _StationsResult.fromJson(Map<String, dynamic> json) =
_$_StationsResult.fromJson;
@override
String get name;
@override
List<String>? get stoppedAtBy;
@override
@JsonKey(ignore: true)
_$$_StationsResultCopyWith<_$_StationsResult> get copyWith =>
throw _privateConstructorUsedError;
}

6
lib/models/stations_result.g.dart

@ -6,15 +6,15 @@ part of 'stations_result.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$_StationsResult _$$_StationsResultFromJson(Map<String, dynamic> json) => StationsResult _$StationsResultFromJson(Map<String, dynamic> json) =>
_$_StationsResult( StationsResult(
name: json['name'] as String, name: json['name'] as String,
stoppedAtBy: (json['stoppedAtBy'] as List<dynamic>?) stoppedAtBy: (json['stoppedAtBy'] as List<dynamic>?)
?.map((e) => e as String) ?.map((e) => e as String)
.toList(), .toList(),
); );
Map<String, dynamic> _$$_StationsResultToJson(_$_StationsResult instance) => Map<String, dynamic> _$StationsResultToJson(StationsResult instance) =>
<String, dynamic>{ <String, dynamic>{
'name': instance.name, 'name': instance.name,
'stoppedAtBy': instance.stoppedAtBy, 'stoppedAtBy': instance.stoppedAtBy,

375
lib/models/train_data.dart

@ -4,118 +4,181 @@
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
@freezed class TrainData {
class TrainData with _$TrainData { TrainData({
const TrainData._(); required this.date,
required this.number,
const factory TrainData({ required this.operator,
required String rank, required this.rank,
required String number, required this.route,
required String date, required this.stations,
required String operator, this.status,
required List<TrainDataGroup> groups, });
}) = _TrainData;
final String date;
factory TrainData.fromJson(Map<String, dynamic> json) => _$TrainDataFromJson(json); final String number;
final String operator;
TrainDataGroup get idealGroup { final String rank;
var result = groups.first; final Route route;
final List<Station> stations;
for (final group in groups) { final TrainDataStatus? status;
if (result.stations.map((s) => s.linkName).toSet().difference(group.stations.map((s) => s.linkName).toSet()).isEmpty) {
result = group; factory TrainData.fromJson(Map<String, dynamic> json) => TrainData(
} date: json["date"],
} number: json["number"],
operator: json["operator"],
return result; rank: json["rank"],
} route: Route.fromJson(json["route"]),
stations: List<Station>.from(
List<TrainDataStation> get stations => idealGroup.stations; json["stations"].map((x) => Station.fromJson(x))),
TrainDataRoute get route => idealGroup.route; status: json["status"] == null
TrainDataStatus? get status => idealGroup.status; ? null
} : TrainDataStatus.fromJson(json["status"]),
);
@freezed Map<String, dynamic> toJson() => {
class TrainDataGroup with _$TrainDataGroup { "date": date,
const factory TrainDataGroup({ "number": number,
required TrainDataRoute route, "operator": operator,
required List<TrainDataStation> stations, "rank": rank,
TrainDataStatus? status, "route": route.toJson(),
}) = _TrainDataGroup; "stations": List<dynamic>.from(stations.map((x) => x.toJson())),
"status": status?.toJson(),
factory TrainDataGroup.fromJson(Map<String, dynamic> json) => _$TrainDataGroupFromJson(json); };
} }
/// Route of the train /// Route of the train
@freezed class Route {
class TrainDataRoute with _$TrainDataRoute { Route({
const factory TrainDataRoute({ required this.from,
required String from, required this.to,
required String to, });
}) = _TrainDataRoute;
final String from;
factory TrainDataRoute.fromJson(Map<String, dynamic> json) => _$TrainDataRouteFromJson(json); final String to;
factory Route.fromJson(Map<String, dynamic> json) => Route(
from: json["from"],
to: json["to"],
);
Map<String, dynamic> toJson() => {
"from": from,
"to": to,
};
} }
@freezed class Station {
class TrainDataStation with _$TrainDataStation { Station({
const factory TrainDataStation({ this.arrival,
required String name, this.departure,
required String linkName, required this.km,
required int km, required this.name,
int? stoppingTime, this.platform,
String? platform, this.stoppingTime,
StationArrDepTime? arrival, });
StationArrDepTime? departure,
@TrainDataNoteConverter() final StationArrDepTime? arrival;
required List<TrainDataNote> notes, final StationArrDepTime? departure;
}) = _TrainDataStation; final int km;
final String name;
factory TrainDataStation.fromJson(Map<String, dynamic> json) => _$TrainDataStationFromJson(json); 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"] == null ? null : json["platform"],
stoppingTime:
json["stoppingTime"] == null ? null : json["stoppingTime"],
);
Map<String, dynamic> toJson() => {
"arrival": arrival?.toJson(),
"departure": departure?.toJson(),
"km": km,
"name": name,
"platform": platform == null ? null : platform,
"stoppingTime": stoppingTime == null ? null : stoppingTime,
};
} }
@freezed class StationArrDepTime {
class StationArrDepTime with _$StationArrDepTime { StationArrDepTime({
const factory StationArrDepTime({ required this.scheduleTime,
required DateTime scheduleTime, this.status,
StationArrDepTimeStatus? status, });
}) = _StationArrDepTime;
final DateTime scheduleTime;
final StationArrDepTimeStatus? status;
factory StationArrDepTime.fromJson(Map<String, dynamic> json) => _$StationArrDepTimeFromJson(json); factory StationArrDepTime.fromJson(Map<String, dynamic> json) =>
StationArrDepTime(
scheduleTime: DateTime.parse(json["scheduleTime"] as String),
status: json["status"] == null ? null : StationArrDepTimeStatus.fromJson(json["status"]),
);
Map<String, dynamic> toJson() => {
"scheduleTime": scheduleTime.toIso8601String(),
"status": status?.toJson(),
};
} }
@freezed class StationArrDepTimeStatus {
class StationArrDepTimeStatus with _$StationArrDepTimeStatus { StationArrDepTimeStatus({
const factory StationArrDepTimeStatus({ required this.delay,
required int delay, required this.real,
required bool real, });
required bool cancelled,
}) = _StationArrDepTimeStatus; final int delay;
final bool real;
factory StationArrDepTimeStatus.fromJson(Map<String, dynamic> json) =>
StationArrDepTimeStatus(
delay: json["delay"],
real: json["real"],
);
factory StationArrDepTimeStatus.fromJson(Map<String, dynamic> json) => _$StationArrDepTimeStatusFromJson(json); Map<String, dynamic> toJson() => {
"delay": delay,
"real": real,
};
} }
@freezed class TrainDataStatus {
class TrainDataStatus with _$TrainDataStatus { TrainDataStatus({
const TrainDataStatus._(); required this.delay,
required this.state,
required this.station,
});
final int delay;
final State state;
final String station;
const factory TrainDataStatus({ factory TrainDataStatus.fromJson(Map<String, dynamic> json) =>
required int delay, TrainDataStatus(
required String station, delay: json["delay"],
required TrainDataStatusState state, state: stateValues.map[json["state"]]!,
}) = _TrainDataStatus; station: json["station"],
);
factory TrainDataStatus.fromJson(Map<String, dynamic> json) => _$TrainDataStatusFromJson(json); Map<String, dynamic> toJson() => {
"delay": delay,
"state": stateValues.reverse[state],
"station": station,
};
@override @override
String toString() { String toString() {
@ -127,118 +190,40 @@ class TrainDataStatus with _$TrainDataStatus {
result += '${delay.abs()} min'; result += '${delay.abs()} min';
} }
result += ' la '; result += ' la ';
result += switch (state) { switch (state) {
TrainDataStatusState.passing => 'trecerea fără oprire prin', case State.PASSING:
TrainDataStatusState.arrival => 'sosirea în', result += 'trecerea fără oprire prin';
TrainDataStatusState.departure => 'plecarea din', break;
}; case State.ARRIVAL:
result += 'sosirea în';
break;
case State.DEPARTURE:
result += 'plecarea din';
break;
}
result += station; result += station;
return result; return result;
} }
} }
enum TrainDataStatusState { passing, arrival, departure } enum State { PASSING, ARRIVAL, DEPARTURE }
abstract class TrainDataNote {
final String kind;
const TrainDataNote({required this.kind}); final stateValues = EnumValues({
"arrival": State.ARRIVAL,
"departure": State.DEPARTURE,
"passing": State.PASSING
});
Map<String, dynamic> toJson() => { class EnumValues<T> {
"kind": kind, Map<String, T> map;
}; Map<T, String>? reverseMap;
}
class TrainDataNoteConverter implements JsonConverter<TrainDataNote, Map<String, dynamic>> {
const TrainDataNoteConverter();
@override EnumValues(this.map);
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),
};
}
@override Map<T, String> get reverse {
Map<String, dynamic> toJson(TrainDataNote object) { if (reverseMap == null) {
return object.toJson(); reverseMap = map.map((k, v) => new MapEntry(v, k));
}
return reverseMap!;
} }
} }
@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

@ -1,217 +0,0 @@
// 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,
};

42
lib/models/train_operator_lines.dart

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

42
lib/models/train_operator_lines.g.dart

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

15
lib/models/trains_result.dart

@ -1,15 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'trains_result.g.dart';
part 'trains_result.freezed.dart';
@freezed
class TrainsResult with _$TrainsResult {
const factory TrainsResult({
required String rank,
required String number,
required String company,
}) = _TrainsResult;
factory TrainsResult.fromJson(Map<String, dynamic> json) => _$TrainsResultFromJson(json);
}

187
lib/models/trains_result.freezed.dart

@ -1,187 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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, unnecessary_question_mark
part of 'trains_result.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
TrainsResult _$TrainsResultFromJson(Map<String, dynamic> json) {
return _TrainsResult.fromJson(json);
}
/// @nodoc
mixin _$TrainsResult {
String get rank => throw _privateConstructorUsedError;
String get number => throw _privateConstructorUsedError;
String get company => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$TrainsResultCopyWith<TrainsResult> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TrainsResultCopyWith<$Res> {
factory $TrainsResultCopyWith(
TrainsResult value, $Res Function(TrainsResult) then) =
_$TrainsResultCopyWithImpl<$Res, TrainsResult>;
@useResult
$Res call({String rank, String number, String company});
}
/// @nodoc
class _$TrainsResultCopyWithImpl<$Res, $Val extends TrainsResult>
implements $TrainsResultCopyWith<$Res> {
_$TrainsResultCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? rank = null,
Object? number = null,
Object? company = null,
}) {
return _then(_value.copyWith(
rank: null == rank
? _value.rank
: rank // ignore: cast_nullable_to_non_nullable
as String,
number: null == number
? _value.number
: number // ignore: cast_nullable_to_non_nullable
as String,
company: null == company
? _value.company
: company // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$_TrainsResultCopyWith<$Res>
implements $TrainsResultCopyWith<$Res> {
factory _$$_TrainsResultCopyWith(
_$_TrainsResult value, $Res Function(_$_TrainsResult) then) =
__$$_TrainsResultCopyWithImpl<$Res>;
@override
@useResult
$Res call({String rank, String number, String company});
}
/// @nodoc
class __$$_TrainsResultCopyWithImpl<$Res>
extends _$TrainsResultCopyWithImpl<$Res, _$_TrainsResult>
implements _$$_TrainsResultCopyWith<$Res> {
__$$_TrainsResultCopyWithImpl(
_$_TrainsResult _value, $Res Function(_$_TrainsResult) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? rank = null,
Object? number = null,
Object? company = null,
}) {
return _then(_$_TrainsResult(
rank: null == rank
? _value.rank
: rank // ignore: cast_nullable_to_non_nullable
as String,
number: null == number
? _value.number
: number // ignore: cast_nullable_to_non_nullable
as String,
company: null == company
? _value.company
: company // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_TrainsResult implements _TrainsResult {
const _$_TrainsResult(
{required this.rank, required this.number, required this.company});
factory _$_TrainsResult.fromJson(Map<String, dynamic> json) =>
_$$_TrainsResultFromJson(json);
@override
final String rank;
@override
final String number;
@override
final String company;
@override
String toString() {
return 'TrainsResult(rank: $rank, number: $number, company: $company)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_TrainsResult &&
(identical(other.rank, rank) || other.rank == rank) &&
(identical(other.number, number) || other.number == number) &&
(identical(other.company, company) || other.company == company));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, rank, number, company);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_TrainsResultCopyWith<_$_TrainsResult> get copyWith =>
__$$_TrainsResultCopyWithImpl<_$_TrainsResult>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_TrainsResultToJson(
this,
);
}
}
abstract class _TrainsResult implements TrainsResult {
const factory _TrainsResult(
{required final String rank,
required final String number,
required final String company}) = _$_TrainsResult;
factory _TrainsResult.fromJson(Map<String, dynamic> json) =
_$_TrainsResult.fromJson;
@override
String get rank;
@override
String get number;
@override
String get company;
@override
@JsonKey(ignore: true)
_$$_TrainsResultCopyWith<_$_TrainsResult> get copyWith =>
throw _privateConstructorUsedError;
}

21
lib/models/trains_result.g.dart

@ -1,21 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'trains_result.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_TrainsResult _$$_TrainsResultFromJson(Map<String, dynamic> json) =>
_$_TrainsResult(
rank: json['rank'] as String,
number: json['number'] as String,
company: json['company'] as String,
);
Map<String, dynamic> _$$_TrainsResultToJson(_$_TrainsResult instance) =>
<String, dynamic>{
'rank': instance.rank,
'number': instance.number,
'company': instance.company,
};

11
lib/models/ui_design.dart

@ -1,15 +1,6 @@
enum UiDesign { enum UiDesign {
MATERIAL, MATERIAL,
CUPERTINO, CUPERTINO
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 {

94
lib/models/ui_timezone.dart

@ -1,94 +0,0 @@
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}';
}
}

80
lib/pages/about/about_page.dart

@ -1,80 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/api/releases.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_fluent.dart';
import 'package:info_tren/pages/about/about_page_material.dart';
import 'package:info_tren/providers.dart';
import 'package:package_info_plus/package_info_plus.dart';
class AboutPage extends ConsumerWidget {
const AboutPage({super.key});
static String routeName = '/about';
@override
Widget build(BuildContext context, WidgetRef ref) {
final uiDesign = ref.watch(uiDesignProvider);
switch (uiDesign) {
case UiDesign.MATERIAL:
return const AboutPageMaterial();
case UiDesign.CUPERTINO:
return const AboutPageCupertino();
case UiDesign.FLUENT:
return const AboutPageFluent();
default:
throw UnmatchedUiDesignException(uiDesign);
}
}
}
abstract class AboutPageShared extends StatefulWidget {
const AboutPageShared({super.key});
}
abstract class AboutPageState<T extends AboutPageShared> extends State<T> {
static const String download = String.fromEnvironment('DOWNLOAD');
final String pageTitle = 'Despre aplicație';
final String versionTitleText = 'Versiunea aplicației';
final String latestVersionText = 'Cea mai recentă versiune';
final String currentVersionText = 'Versiunea curentă';
List<ChangelogEntry> localChangelog = [];
List<ChangelogEntry> remoteChangelog = [];
List<ChangelogEntry> get mergedChangelogs {
final logs = remoteChangelog.toList();
final versions = logs.map((log) => log.version).toSet();
for (final log in localChangelog) {
if (!versions.contains(log.version)) {
logs.add(log);
versions.add(log.version);
}
}
logs.sort((l1, l2) => l2.version.compareTo(l1.version));
return logs;
}
PackageInfo? packageInfo;
@override
void didChangeDependencies() {
PackageInfo.fromPlatform().then((value) => setState(() {
packageInfo = value;
}));
DefaultAssetBundle.of(context).loadString('CHANGELOG.txt').then((value) {
setState(() {
localChangelog = ChangelogEntry.fromTextFile(value);
});
});
getRemoteReleases().then((value) => setState(() {
remoteChangelog = value;
}));
super.didChangeDependencies();
}
}

137
lib/pages/about/about_page_cupertino.dart

@ -1,137 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:info_tren/components/cupertino_divider.dart';
import 'package:info_tren/pages/about/about_page.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutPageCupertino extends AboutPageShared {
const AboutPageCupertino({super.key});
@override
State<AboutPageShared> createState() => AboutPageStateCupertino();
}
class AboutPageStateCupertino extends AboutPageState<AboutPageCupertino> {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(pageTitle),
),
child: Builder(
builder: (context) {
final topPadding = MediaQuery.of(context).padding.top;
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: topPadding,
),
Center(
child: Text(
'Info Tren',
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
),
),
if (packageInfo != null)
Center(
child: Text(
packageInfo!.packageName,
style: const TextStyle(
inherit: true,
fontSize: 14,
),
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: CupertinoDivider(),
),
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: const TextStyle(
inherit: true,
fontSize: 24,
),
),
),
if (localChangelog.isNotEmpty && log.version == localChangelog.first.version)
Container(
decoration: BoxDecoration(
border: Border.all(
color: CupertinoTheme.of(context).textTheme.textStyle.color ?? CupertinoColors.inactiveGray,
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: CupertinoColors.activeGreen,
width: 1,
),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
latestVersionText,
style: const TextStyle(
inherit: true,
color: CupertinoColors.activeGreen,
),
),
),
),
if (AboutPageState.download == 'apk' && log.apkLink != null)
CupertinoButton(
padding: const EdgeInsets.all(4),
minSize: 0,
onPressed: () {
launchUrl(
log.apkLink!,
mode: LaunchMode.externalApplication,
);
},
child: const Icon(CupertinoIcons.arrow_down_circle),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RichText(
text: TextSpan(
text: log.description,
),
),
),
const CupertinoDivider(),
],
],
),
);
}
),
);
}
}

198
lib/pages/about/about_page_fluent.dart

@ -1,198 +0,0 @@
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,
),
),
),
],
],
),
),
);
}
}

199
lib/pages/about/about_page_material.dart

@ -1,199 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:info_tren/pages/about/about_page.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutPageMaterial extends AboutPageShared {
const AboutPageMaterial({super.key});
@override
State<AboutPageShared> createState() => AboutPageStateMaterial();
}
class AboutPageStateMaterial extends AboutPageState<AboutPageMaterial> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(pageTitle),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
'Info Tren',
style: Theme.of(context).textTheme.displayMedium,
),
),
if (packageInfo != null)
Center(
child: Text(
packageInfo!.packageName,
style: Theme.of(context).textTheme.bodySmall,
),
),
// 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: Theme.of(context).textTheme.headlineMedium,
),
),
if (localChangelog.isNotEmpty && log.version == localChangelog.first.version)
Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.onBackground,
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: const 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(Icons.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(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),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RichText(
text: TextSpan(
text: log.description,
),
),
),
],
],
),
),
);
}
}

64
lib/pages/main/main_page.dart

@ -1,29 +1,25 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/models.dart';
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_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/utils/default_ui_design.dart';
class MainPage extends ConsumerWidget { class MainPage extends StatelessWidget {
const MainPage({super.key,}); final UiDesign? uiDesign;
const MainPage({ Key? key, this.uiDesign }) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context) {
final uiDesign = ref.watch(uiDesignProvider); final uiDesign = this.uiDesign ?? defaultUiDesign;
switch (uiDesign) { switch (uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
return const MainPageMaterial(); return MainPageMaterial();
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return const MainPageCupertino(); return MainPageCupertino();
case UiDesign.FLUENT:
return const MainPageFluent();
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
@ -32,43 +28,22 @@ class MainPage extends ConsumerWidget {
abstract class MainPageShared extends StatelessWidget { abstract class MainPageShared extends StatelessWidget {
final String pageTitle = 'Info Tren'; final String pageTitle = 'Info Tren';
final String moreOptionsText = 'Mai multe opțiuni';
const MainPageShared({super.key});
List<MainPageAction> get popupMenu => [ List<MainPageOption> get options => [
MainPageAction( MainPageOption(
name: 'Setări',
action: (context) {
Navigator.of(context).pushNamed(SettingsPage.routeName);
},
),
MainPageAction(
name: 'Despre aplicație',
action: (context) {
Navigator.of(context).pushNamed(AboutPage.routeName);
},
),
];
List<MainPageAction> get options => [
MainPageAction(
name: 'Informații despre tren', name: 'Informații despre tren',
description: 'Află informații despre parcursul unui anumit tren', action: (BuildContext context) {
action: (context) {
onTrainInfoPageInvoke(context); onTrainInfoPageInvoke(context);
}, },
), ),
MainPageAction( MainPageOption(
name: 'Tabelă plecari/sosiri', name: 'Tabelă plecari/sosiri',
description: 'Vezi trenurile care pleacă și sosesc dintr-o gară',
action: (context) { action: (context) {
onStationBoardPageInvoke(context); onStationBoardPageInvoke(context);
}, },
), ),
MainPageAction( MainPageOption(
name: 'Planificare rută', name: 'Planificare rută',
description: 'Găsește trenurile disponibile pentru călătoria între două gări',
// TODO: Implement route planning // TODO: Implement route planning
action: null, action: null,
), ),
@ -87,14 +62,9 @@ abstract class MainPageShared extends StatelessWidget {
} }
} }
class MainPageAction { class MainPageOption {
final String name; final String name;
final String? description;
final void Function(BuildContext context)? action; final void Function(BuildContext context)? action;
MainPageAction({ MainPageOption({required this.name, this.action});
required this.name,
this.action,
this.description,
});
} }

47
lib/pages/main/main_page_cupertino.dart

@ -2,64 +2,19 @@ import 'package:flutter/cupertino.dart';
import 'package:info_tren/pages/main/main_page.dart'; import 'package:info_tren/pages/main/main_page.dart';
class MainPageCupertino extends MainPageShared { class MainPageCupertino extends MainPageShared {
const MainPageCupertino({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar( navigationBar: CupertinoNavigationBar(
middle: Text(pageTitle), middle: Text(pageTitle),
trailing: CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () {
showCupertinoModalPopup(
context: context,
builder: (context) {
return CupertinoActionSheet(
actions: popupMenu.map((m) => CupertinoActionSheetAction(
onPressed: () {
Navigator.of(context).pop();
m.action?.call(context);
},
child: Text(m.name),
)).toList(),
cancelButton: CupertinoActionSheetAction(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Anulare'),
),
);
},
);
},
child: const Icon(CupertinoIcons.ellipsis_circle),
),
), ),
child: SafeArea( child: SafeArea(
child: Center( child: Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: options.map((option) => CupertinoButton.filled( children: options.map((option) => CupertinoButton.filled(
child: Text(option.name),
onPressed: option.action == null ? null : () => option.action!(context), onPressed: option.action == null ? null : () => option.action!(context),
child: Text.rich(
TextSpan(
children: [
TextSpan(text: option.name),
if (option.description != null) ...[
const TextSpan(text: '\n'),
TextSpan(
text: option.description,
style: const TextStyle(
inherit: true,
fontSize: 14,
),
),
],
],
),
textAlign: TextAlign.center,
),
)).map((w) => Padding( )).map((w) => Padding(
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), padding: const EdgeInsets.fromLTRB(4, 2, 4, 2),
child: SizedBox( child: SizedBox(

89
lib/pages/main/main_page_fluent.dart

@ -1,89 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:info_tren/pages/main/main_page.dart';
class MainPageFluent extends MainPageShared {
const MainPageFluent({super.key});
@override
Widget build(BuildContext context) {
return NavigationView(
appBar: NavigationAppBar(
automaticallyImplyLeading: false,
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,
// actions: [
// PopupMenuButton<int>(
// icon: const Icon(Icons.more_vert),
// tooltip: moreOptionsText,
// itemBuilder: (_) => popupMenu.asMap().entries.map((e) => PopupMenuItem(
// value: e.key,
// child: Text(e.value.name),
// )).toList(),
// onSelected: (index) {
// popupMenu[index].action?.call(context);
// },
// ),
// ],
),
content: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: options.map((option) => HoverButton(
onPressed: option.action != null ? () => option.action!(context) : null,
builder: (context, states) {
return Card(
backgroundColor: option.action != null ? (states.isHovering ? FluentTheme.of(context).accentColor.dark : FluentTheme.of(context).accentColor) : null,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
option.name,
style: FluentTheme.of(context)
.typography
.bodyLarge
?.copyWith(
color:
FluentTheme.of(context).activeColor,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(option.description!),
),
],
),
);
},
)).map((w) => Padding(
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2),
child: SizedBox(
width: double.infinity,
child: w,
),
)).toList(),
),
),
),
);
}
}

42
lib/pages/main/main_page_material.dart

@ -2,55 +2,23 @@ import 'package:flutter/material.dart';
import 'package:info_tren/pages/main/main_page.dart'; import 'package:info_tren/pages/main/main_page.dart';
class MainPageMaterial extends MainPageShared { class MainPageMaterial extends MainPageShared {
const MainPageMaterial({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(pageTitle), title: Text(pageTitle),
centerTitle: true, centerTitle: true,
actions: [
PopupMenuButton<int>(
icon: const Icon(Icons.more_vert),
tooltip: moreOptionsText,
itemBuilder: (_) => popupMenu.asMap().entries.map((e) => PopupMenuItem(
value: e.key,
child: Text(e.value.name),
)).toList(),
onSelected: (index) {
popupMenu[index].action?.call(context);
},
),
],
), ),
body: SafeArea( body: SafeArea(
child: Center( child: Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: options.map((option) => Card( children: options.map((option) => ElevatedButton(
color: option.action != null ? Theme.of(context).colorScheme.secondaryContainer : null, child: Text(
child: InkWell( option.name,
onTap: option.action != null ? () => option.action!(context) : null, style: Theme.of(context).textTheme.button?.copyWith(fontSize: 18),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
option.name,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(option.description!),
),
],
),
), ),
onPressed: option.action != null ? () => option.action!(context) : null,
)).map((w) => Padding( )).map((w) => Padding(
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), padding: const EdgeInsets.fromLTRB(4, 2, 4, 2),
child: SizedBox( child: SizedBox(

38
lib/pages/settings/setings_page.dart

@ -1,38 +0,0 @@
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

@ -1,96 +0,0 @@
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

@ -1,65 +0,0 @@
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

@ -1,66 +0,0 @@
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);
}
},
),
);
},
),
],
),
),
);
}
}

46
lib/pages/station_arrdep_page/select_station/select_station.dart

@ -1,39 +1,31 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/models.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_cupertino.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_cupertino.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_fluent.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_material.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_material.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/utils/default_ui_design.dart';
import 'package:info_tren/api/stations.dart' as api_stations; import 'package:info_tren/api/stations.dart' as apiStations;
class SelectStationPage extends ConsumerWidget { class SelectStationPage extends StatefulWidget {
const SelectStationPage({ super.key }); final UiDesign? uiDesign;
const SelectStationPage({ Key? key, this.uiDesign }) : super(key: key);
static String routeName = '/stationArrDep/selectStation'; static String routeName = '/stationArrDep/selectStation';
@override @override
Widget build(BuildContext context, WidgetRef ref) { SelectStationPageState createState() {
final uiDesign = ref.watch(uiDesignProvider); final uiDesign = this.uiDesign ?? defaultUiDesign;
switch (uiDesign) { switch (uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
return const SelectStationPageMaterial(); return SelectStationPageStateMaterial();
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return const SelectStationPageCupertino(); return SelectStationPageStateCupertino();
case UiDesign.FLUENT:
return const SelectStationPageFluent();
default:
throw UnmatchedUiDesignException(uiDesign);
} }
} }
} }
abstract class SelectStationPageShared extends StatefulWidget { abstract class SelectStationPageState extends State<SelectStationPage> {
const SelectStationPageShared({super.key});
}
abstract class SelectStationPageState extends State<SelectStationPageShared> {
static const pageTitle = 'Plecări/sosiri stație'; static const pageTitle = 'Plecări/sosiri stație';
static const textFieldLabel = 'Numele stației'; static const textFieldLabel = 'Numele stației';
static const roToEn = { static const roToEn = {
@ -43,9 +35,6 @@ abstract class SelectStationPageState extends State<SelectStationPageShared> {
'ș': 's', 'ș': 's',
'ț': 't', 'ț': 't',
}; };
final textEditingController = TextEditingController();
List<String> stations = []; List<String> stations = [];
List<String> get filteredStations { List<String> get filteredStations {
@ -69,7 +58,7 @@ abstract class SelectStationPageState extends State<SelectStationPageShared> {
@override @override
void initState() { void initState() {
api_stations.stations.then((value) { apiStations.stations.then((value) {
setState(() { setState(() {
stations = value.map((e) => e.name).toList(growable: false,); stations = value.map((e) => e.name).toList(growable: false,);
}); });
@ -83,10 +72,9 @@ abstract class SelectStationPageState extends State<SelectStationPageShared> {
} }
void onSuggestionSelected(String suggestion) { void onSuggestionSelected(String suggestion) {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(ViewStationPage.routeName, arguments: suggestion);
ViewStationPage.routeName,
arguments: ViewStationArguments(stationName: suggestion),
);
} }
final TextEditingController textEditingController = TextEditingController();
} }

11
lib/pages/station_arrdep_page/select_station/select_station_cupertino.dart

@ -2,18 +2,11 @@ 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/pages/station_arrdep_page/select_station/select_station.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
class SelectStationPageCupertino extends SelectStationPageShared {
const SelectStationPageCupertino({super.key});
@override
State<StatefulWidget> createState() => SelectStationPageStateCupertino();
}
class SelectStationPageStateCupertino extends SelectStationPageState { class SelectStationPageStateCupertino extends SelectStationPageState {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar( navigationBar: CupertinoNavigationBar(
middle: Text(SelectStationPageState.pageTitle), middle: Text(SelectStationPageState.pageTitle),
), ),
child: SafeArea( child: SafeArea(
@ -45,7 +38,7 @@ class SelectStationPageStateCupertino extends SelectStationPageState {
), ),
onTap: () => onSuggestionSelected(filteredStations[index]), onTap: () => onSuggestionSelected(filteredStations[index]),
), ),
const CupertinoDivider(), CupertinoDivider(),
], ],
); );
}, },

59
lib/pages/station_arrdep_page/select_station/select_station_fluent.dart

@ -1,59 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
class SelectStationPageFluent extends SelectStationPageShared {
const SelectStationPageFluent({super.key});
@override
State<StatefulWidget> createState() => SelectStationPageStateFluent();
}
class SelectStationPageStateFluent extends SelectStationPageState {
@override
Widget build(BuildContext context) {
return NavigationView(
appBar: const NavigationAppBar(
title: Text(SelectStationPageState.pageTitle),
// centerTitle: true,
),
content: SafeArea(
bottom: false,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Padding(
padding: const EdgeInsets.all(4),
child: TextBox(
controller: textEditingController,
autofocus: true,
placeholder: SelectStationPageState.textFieldLabel,
textInputAction: TextInputAction.search,
onChanged: onTextChanged,
),
),
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
// dense: true,
title: Text(filteredStations[index]),
onPressed: () => onSuggestionSelected(filteredStations[index]),
),
const Divider(
size: 1,
),
],
);
},
itemCount: filteredStations.length,
),
),
],
),
),
);
}
}

14
lib/pages/station_arrdep_page/select_station/select_station_material.dart

@ -1,19 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.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';
class SelectStationPageMaterial extends SelectStationPageShared {
const SelectStationPageMaterial({super.key});
@override
State<StatefulWidget> createState() => SelectStationPageStateMaterial();
}
class SelectStationPageStateMaterial extends SelectStationPageState { class SelectStationPageStateMaterial extends SelectStationPageState {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text(SelectStationPageState.pageTitle), title: Text(SelectStationPageState.pageTitle),
centerTitle: true, centerTitle: true,
), ),
body: SafeArea( body: SafeArea(
@ -26,7 +20,7 @@ class SelectStationPageStateMaterial extends SelectStationPageState {
child: TextField( child: TextField(
controller: textEditingController, controller: textEditingController,
autofocus: true, autofocus: true,
decoration: const InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
labelText: SelectStationPageState.textFieldLabel, labelText: SelectStationPageState.textFieldLabel,
), ),
@ -45,7 +39,7 @@ class SelectStationPageStateMaterial extends SelectStationPageState {
title: Text(filteredStations[index]), title: Text(filteredStations[index]),
onTap: () => onSuggestionSelected(filteredStations[index]), onTap: () => onSuggestionSelected(filteredStations[index]),
), ),
const Divider( Divider(
height: 1, height: 1,
), ),
], ],

118
lib/pages/station_arrdep_page/view_station/view_station.dart

@ -1,100 +1,84 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:info_tren/api/station_data.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/components/refresh_future_builder.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models/station_data.dart';
import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_cupertino.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_cupertino.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_fluent.dart';
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_material.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_material.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/providers.dart'; import 'package:info_tren/utils/default_ui_design.dart';
class ViewStationPage extends HookConsumerWidget { class ViewStationPage extends StatefulWidget {
const ViewStationPage({ super.key, }); final UiDesign? uiDesign;
final String stationName;
const ViewStationPage({ Key? key, required this.stationName, this.uiDesign }) : super(key: key);
static String routeName = '/stationArrDep/viewStation'; static String routeName = '/stationArrDep/viewStation';
ValueNotifier<ViewStationPageTab> useTab() => useState(ViewStationPageTab.departures);
@override @override
Widget build(BuildContext context, WidgetRef ref) { ViewStationPageState createState() {
final tab = useTab(); final uiDesign = this.uiDesign ?? defaultUiDesign;
final uiDesign = ref.watch(uiDesignProvider);
switch (uiDesign) { switch (uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
return ViewStationPageMaterial( return ViewStationPageStateMaterial();
tab: tab.value,
setTab: (newTab) => tab.value = newTab,
);
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return ViewStationPageCupertino( return ViewStationPageStateCupertino();
tab: tab.value,
setTab: (newTab) => tab.value = newTab,
);
case UiDesign.FLUENT:
return ViewStationPageFluent(
tab: tab.value,
setTab: (newTab) => tab.value = newTab,
);
} }
} }
} }
class ViewStationArguments { abstract class ViewStationPageState extends State<ViewStationPage> {
final String stationName;
final DateTime? date;
const ViewStationArguments({required this.stationName, this.date});
}
abstract class ViewStationPageShared extends StatelessWidget {
static const arrivals = 'Sosiri'; static const arrivals = 'Sosiri';
static const departures = 'Plecări'; static const departures = 'Pleacări';
static const loadingText = 'Se încarcă...'; static const loadingText = 'Se încarcă...';
static const arrivesFrom = 'Sosește de la'; static const arrivesFrom = 'Sosește de la';
static const arrivedFrom = 'A sosit de la';
static const cancelledArrival = 'Anulat - de la';
static const departsTo = 'Pleacă către'; static const departsTo = 'Pleacă către';
static const departedTo = 'A plecat către';
static const cancelledDeparture = 'Anulat - către';
static const errorText = 'A apărut o eroare';
static const retryText = 'Reîncearcă';
final ViewStationPageTab tab; ViewStationPageTab tab = ViewStationPageTab.departures;
final void Function(ViewStationPageTab) setTab; late String stationName;
const ViewStationPageShared({required this.tab, required this.setTab, super.key}); late Future<StationData> Function() futureCreator;
@override
void initState() {
initData();
super.initState();
}
@override
void didChangeDependencies() {
if (stationName != widget.stationName) {
setState(() {
initData();
});
}
super.didChangeDependencies();
}
void initData() {
stationName = widget.stationName;
futureCreator = () => getStationData(stationName);
}
Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future<StationData> Function() newFutureBuilder) _, RefreshFutureBuilderSnapshot<StationData> snapshot); Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future<StationData> Function() newFutureBuilder) _, RefreshFutureBuilderSnapshot<StationData> snapshot);
Widget buildStationArrivalItem(BuildContext context, StationArrDep item); Widget buildStationArrivalItem(BuildContext context, StationArrival item);
Widget buildStationDepartureItem(BuildContext context, StationArrDep item); Widget buildStationDepartureItem(BuildContext context, StationDeparture item);
void onTrainTapped(BuildContext context, StationTrain train) { void onTabChange(int index) {
Navigator.of(context).pushNamed( setState(() {
TrainInfo.routeName, tab = ViewStationPageTab.values[index];
arguments: TrainInfoArguments( });
trainNumber: train.number,
date: train.departureDate,
),
);
} }
void onTabChange (int index) { void onTrainTapped(String trainNumber) {
setTab(ViewStationPageTab.values[index]); Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: trainNumber);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer( return RefreshFutureBuilder(
builder: (context, ref, _) { futureCreator: futureCreator,
return RefreshFutureBuilderProviderAdapter( builder: buildContent,
futureProvider: viewStationDataProvider,
refresh: () async {
ref.invalidate(stationDataProvider);
},
builder: buildContent,
);
}
); );
} }
} }
@ -102,4 +86,4 @@ abstract class ViewStationPageShared extends StatelessWidget {
enum ViewStationPageTab { enum ViewStationPageTab {
arrivals, arrivals,
departures, departures,
} }

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

@ -1,37 +1,28 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
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:flutter/src/widgets/framework.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/station_data.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';
class ViewStationPageCupertino extends ViewStationPageShared {
const ViewStationPageCupertino({super.key, required super.tab, required super.setTab});
class ViewStationPageStateCupertino extends ViewStationPageState {
@override @override
Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future<StationData> Function()) _, RefreshFutureBuilderSnapshot<StationData> snapshot) { Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future<StationData> Function()) _, RefreshFutureBuilderSnapshot<StationData> snapshot) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar( navigationBar: CupertinoNavigationBar(
middle: Consumer( middle: Text(snapshot.hasData ? snapshot.data!.stationName : stationName),
builder: (context, ref, _) {
final stationName = ref.watch(viewStationArgumentsProvider.select((value) => value.stationName));
return Text(snapshot.hasData ? snapshot.data!.stationName : stationName);
}
),
), ),
child: snapshot.hasData ? CupertinoTabScaffold( child: snapshot.hasData ? CupertinoTabScaffold(
tabBar: CupertinoTabBar( tabBar: CupertinoTabBar(
items: const [ items: [
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(CupertinoIcons.arrow_down), icon: Icon(CupertinoIcons.arrow_down),
label: ViewStationPageShared.arrivals, label: ViewStationPageState.arrivals,
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(CupertinoIcons.arrow_up), icon: Icon(CupertinoIcons.arrow_up),
label: ViewStationPageShared.departures, label: ViewStationPageState.departures,
), ),
], ],
onTap: onTabChange, onTap: onTabChange,
@ -57,63 +48,73 @@ class ViewStationPageCupertino extends ViewStationPageShared {
), ),
); );
}, },
) : snapshot.state == RefreshFutureBuilderState.waiting ? const Loading(text: ViewStationPageShared.loadingText,) : Container(), ) : snapshot.state == RefreshFutureBuilderState.waiting ? Loading(text: ViewStationPageState.loadingText, uiDesign: widget.uiDesign,) : Container(),
); );
} }
@override @override
Widget buildStationArrivalItem(BuildContext context, StationArrDep item) { Widget buildStationArrivalItem(BuildContext context, StationArrival item) {
return GestureDetector( return GestureDetector(
onTap: () => onTrainTapped(context, item.train), onTap: () => onTrainTapped(item.train.number),
child: CupertinoFormRow( child: CupertinoFormRow(
child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
prefix: Text.rich( prefix: Text.rich(
trainIdSpan(rank: item.train.rank, number: item.train.number), TextSpan(
children: [
TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
),
),
TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
), ),
helper: Text.rich( helper: Text.rich(
TextSpan( TextSpan(
children: [ children: [
const TextSpan(text: ViewStationPageShared.arrivesFrom), TextSpan(text: ViewStationPageState.arrivesFrom),
const TextSpan(text: ' '), TextSpan(text: ' '),
TextSpan(text: item.train.terminus), TextSpan(text: item.train.origin),
], ],
), ),
), ),
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')}');
},
),
), ),
); );
} }
@override @override
Widget buildStationDepartureItem(BuildContext context, StationArrDep item) { Widget buildStationDepartureItem(BuildContext context, StationDeparture item) {
return GestureDetector( return GestureDetector(
onTap: () => onTrainTapped(context, item.train), onTap: () => onTrainTapped(item.train.number),
child: CupertinoFormRow( child: CupertinoFormRow(
child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
prefix: Text.rich( prefix: Text.rich(
trainIdSpan(rank: item.train.rank, number: item.train.number), TextSpan(
children: [
TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
),
),
TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
), ),
helper: Text.rich( helper: Text.rich(
TextSpan( TextSpan(
children: [ children: [
const TextSpan(text: ViewStationPageShared.departsTo), TextSpan(text: ViewStationPageState.departsTo),
const TextSpan(text: ' '), TextSpan(text: ' '),
TextSpan(text: item.train.terminus), TextSpan(text: item.train.destination),
], ],
), ),
), ),
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')}');
},
),
), ),
); );
} }
} }

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

@ -1,264 +0,0 @@
import 'dart:math';
import 'dart:ui';
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/loading/loading.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/pages/station_arrdep_page/view_station/view_station.dart';
import 'package:info_tren/providers.dart';
class ViewStationPageFluent extends ViewStationPageShared {
const ViewStationPageFluent({super.key, required super.tab, required super.setTab});
@override
Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future<StationData> Function()) _, RefreshFutureBuilderSnapshot<StationData> snapshot) {
return NavigationView(
appBar: NavigationAppBar(
title: Consumer(
builder: (context, ref, _) {
final stationName = ref.watch(viewStationArgumentsProvider.select((value) => value.stationName));
return Text(snapshot.hasData ? snapshot.data!.stationName : stationName);
}
),
),
content: snapshot.state == RefreshFutureBuilderState.waiting || snapshot.state == RefreshFutureBuilderState.refreshError
? const Loading(text: ViewStationPageShared.loadingText,)
: snapshot.state == RefreshFutureBuilderState.error
? Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
FluentIcons.error,
size: 32,
color: Colors.red.normal,
),
const Text(
ViewStationPageShared.errorText,
style: TextStyle(
inherit: true,
fontSize: 32,
),
),
Text(
snapshot.error.toString(),
style: FluentTheme.of(context).typography.subtitle,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Button(
onPressed: () {
refresh();
},
child: const Text(ViewStationPageShared.retryText),
),
),
],
),
),
)
: null,
pane: snapshot.hasData ? NavigationPane(
onChanged: onTabChange,
selected: tab.index,
items: [
PaneItem(
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(child: SafeArea(left: false, bottom: false, right: false,child: Container(),),),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return tab == ViewStationPageTab.arrivals ? buildStationArrivalItem(context, snapshot.data!.arrivals![index]) : buildStationDepartureItem(context, snapshot.data!.departures![index]);
},
childCount: tab == ViewStationPageTab.arrivals ? snapshot.data!.arrivals?.length ?? 0 : snapshot.data!.departures?.length ?? 0,
),
),
],
),
title: const Text(ViewStationPageShared.arrivals),
icon: const Icon(FluentIcons.arrow_down_right8),
),
PaneItem(
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(child: SafeArea(left: false, bottom: false, right: false,child: Container(),),),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return tab == ViewStationPageTab.arrivals ? buildStationArrivalItem(context, snapshot.data!.arrivals![index]) : buildStationDepartureItem(context, snapshot.data!.departures![index]);
},
childCount: tab == ViewStationPageTab.arrivals ? snapshot.data!.arrivals?.length ?? 0 : snapshot.data!.departures?.length ?? 0,
),
),
],
),
title: const Text(ViewStationPageShared.departures),
icon: const Icon(FluentIcons.arrow_up_right8),
),
],
) : null,
// bottomNavigationBar: snapshot.hasData ? BottomNavigationBar(
// items: const [
// BottomNavigationBarItem(
// icon: Icon(Icons.arrow_downward),
// label: ViewStationPageShared.arrivals,
// ),
// BottomNavigationBarItem(
// icon: Icon(Icons.arrow_upward),
// label: ViewStationPageShared.departures,
// ),
// ],
// currentIndex: tab.index,
// onTap: onTabChange,
// ) : null,
);
}
Widget buildStationItem(BuildContext context, StationArrDep item, {required bool arrival}) {
return HoverButton(
onPressed: () => onTrainTapped(context, item.train),
builder: (context, states) {
return Container(
color: item.status.cancelled
? Colors.red.withAlpha(100)
: states.isPressing
? FluentTheme.of(context).scaffoldBackgroundColor
: states.isHovering
? FluentTheme.of(context).inactiveBackgroundColor
: null,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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')}',
style: TextStyle(
inherit: true,
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) Consumer(
builder: (context, ref, _) {
final tz = ref.watch(uiTimeZoneProvider);
final newTime = tz.convertDateTime(item.time.add(Duration(minutes: item.status.delay)));
final delay = item.status.delay > 0;
return Text(
'${newTime.hour.toString().padLeft(2, '0')}:${newTime.minute.toString().padLeft(2, '0')}',
style: TextStyle(
inherit: true,
fontFeatures: const [
FontFeature.tabularFigures(),
],
color: delay ? Colors.red : Colors.green,
),
);
},
),
],
),
),
Expanded(
child: IgnorePointer(
child: ListTile(
// isThreeLine: item.status.delay != 0,
title: Text.rich(
trainIdSpan(rank: item.train.rank, number: item.train.number),
),
subtitle: Text.rich(
TextSpan(
children: [
TextSpan(
text: item.status.cancelled
? (arrival ? ViewStationPageShared.cancelledArrival : ViewStationPageShared.cancelledDeparture)
: item.time.add(Duration(minutes: max(0, item.status.delay))).compareTo(DateTime.now()) < 0
? (arrival ? ViewStationPageShared.arrivedFrom : ViewStationPageShared.departedTo)
: (arrival ? ViewStationPageShared.arrivesFrom : ViewStationPageShared.departsTo)
),
const TextSpan(text: ' '),
TextSpan(text: item.train.terminus),
if (item.status.delay != 0) ...[
const TextSpan(text: '\n'),
if (item.status.delay.abs() >= 60) ...[
TextSpan(text: (item.status.delay.abs() ~/ 60).toString()),
TextSpan(text: item.status.delay.abs() >= 120 ? ' ore' : ' oră'),
if (item.status.delay.abs() % 60 != 0)
const TextSpan(text: ' și '),
],
TextSpan(text: (item.status.delay.abs() % 60).toString()),
TextSpan(text: item.status.delay.abs() > 1 ? ' minute' : ' minut'),
const TextSpan(text: ' '),
if (item.status.delay > 0)
TextSpan(
text: 'întârziere',
style: TextStyle(
inherit: true,
color: Colors.red,
),
)
else
TextSpan(
text: 'mai devreme',
style: TextStyle(
inherit: true,
color: Colors.green,
),
),
],
],
),
),
),
),
),
if (item.status.platform != null)
IntrinsicHeight(
child: AspectRatio(
aspectRatio: 1,
child: Badge(
text: item.status.platform!,
caption: 'Linia',
isOnTime: item.status.real && item.status.delay <= 0,
isDelayed: item.status.real && item.status.delay > 0,
),
),
),
],
),
);
},
);
}
@override
Widget buildStationArrivalItem(BuildContext context, StationArrDep item) {
return buildStationItem(context, item, arrival: true);
}
@override
Widget buildStationDepartureItem(BuildContext context, StationArrDep item) {
return buildStationItem(context, item, arrival: false);
}
}

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

@ -1,90 +1,40 @@
import 'dart:math'; import 'package:flutter/material.dart';
import 'dart:ui';
import 'package:flutter/material.dart' hide Badge;
import 'package:hooks_riverpod/hooks_riverpod.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:flutter/src/widgets/framework.dart';
import 'package:info_tren/models.dart'; import 'package:info_tren/models/station_data.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';
class ViewStationPageMaterial extends ViewStationPageShared {
const ViewStationPageMaterial({super.key, required super.tab, required super.setTab});
class ViewStationPageStateMaterial extends ViewStationPageState {
@override @override
Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future<StationData> Function()) _, RefreshFutureBuilderSnapshot<StationData> snapshot) { Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future<StationData> Function()) _, RefreshFutureBuilderSnapshot<StationData> snapshot) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Consumer( title: Text(snapshot.hasData ? snapshot.data!.stationName : stationName),
builder: (context, ref, _) {
final stationName = ref.watch(viewStationArgumentsProvider.select((value) => value.stationName));
return Text(snapshot.hasData ? snapshot.data!.stationName : stationName);
}
),
centerTitle: true, centerTitle: true,
), ),
body: snapshot.state == RefreshFutureBuilderState.waiting body: snapshot.state == RefreshFutureBuilderState.waiting ? Loading(text: ViewStationPageState.loadingText, uiDesign: widget.uiDesign,) : CustomScrollView(
? const Loading(text: ViewStationPageShared.loadingText,) slivers: [
: snapshot.state == RefreshFutureBuilderState.error SliverToBoxAdapter(child: SafeArea(child: Container(), left: false, bottom: false, right: false,),),
? Padding( SliverList(
padding: const EdgeInsets.all(8.0), delegate: SliverChildBuilderDelegate(
child: Center( (context, index) {
child: Column( return tab == ViewStationPageTab.arrivals ? buildStationArrivalItem(context, snapshot.data!.arrivals![index]) : buildStationDepartureItem(context, snapshot.data!.departures![index]);
mainAxisSize: MainAxisSize.min, },
children: [ childCount: tab == ViewStationPageTab.arrivals ? snapshot.data!.arrivals?.length ?? 0 : snapshot.data!.departures?.length ?? 0,
const Icon(
Icons.error_outline,
size: 32,
color: Colors.red,
),
const Text(
ViewStationPageShared.errorText,
style: TextStyle(
inherit: true,
fontSize: 32,
),
),
Text(
snapshot.error.toString(),
style: Theme.of(context).textTheme.bodySmall,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
refresh();
},
child: const Text(ViewStationPageShared.retryText),
),
),
],
), ),
), ),
) ],
: CustomScrollView( ),
slivers: [
SliverToBoxAdapter(child: SafeArea(left: false, bottom: false, right: false,child: Container(),),),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return tab == ViewStationPageTab.arrivals ? buildStationArrivalItem(context, snapshot.data!.arrivals![index]) : buildStationDepartureItem(context, snapshot.data!.departures![index]);
},
childCount: tab == ViewStationPageTab.arrivals ? snapshot.data!.arrivals?.length ?? 0 : snapshot.data!.departures?.length ?? 0,
),
),
],
),
bottomNavigationBar: snapshot.hasData ? BottomNavigationBar( bottomNavigationBar: snapshot.hasData ? BottomNavigationBar(
items: const [ items: [
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.arrow_downward), icon: Icon(Icons.arrow_downward),
label: ViewStationPageShared.arrivals, label: ViewStationPageState.arrivals,
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.arrow_upward), icon: Icon(Icons.arrow_upward),
label: ViewStationPageShared.departures, label: ViewStationPageState.departures,
), ),
], ],
currentIndex: tab.index, currentIndex: tab.index,
@ -93,136 +43,65 @@ class ViewStationPageMaterial extends ViewStationPageShared {
); );
} }
Widget buildStationItem(BuildContext context, StationArrDep item, {required bool arrival}) { @override
return InkWell( Widget buildStationArrivalItem(BuildContext context, StationArrival item) {
onTap: () => onTrainTapped(context, item.train), return ListTile(
child: Container( leading: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
color: item.status.cancelled ? Colors.red.withAlpha(100) : null, title: Text.rich(
child: Row( TextSpan(
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Padding( TextSpan(
padding: const EdgeInsets.all(8), text: item.train.rank,
child: Column( style: TextStyle(
mainAxisSize: MainAxisSize.min, color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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')}',
style: TextStyle(
inherit: true,
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) Consumer(
builder: (context, ref, _) {
final tz = ref.watch(uiTimeZoneProvider);
final newTime = tz.convertDateTime(item.time.add(Duration(minutes: item.status.delay)));
final delay = item.status.delay > 0;
return Text(
'${newTime.hour.toString().padLeft(2, '0')}:${newTime.minute.toString().padLeft(2, '0')}',
style: TextStyle(
inherit: true,
fontFeatures: const [
FontFeature.tabularFigures(),
],
color: delay ? Colors.red : Colors.green,
),
);
},
),
],
),
),
Expanded(
child: IgnorePointer(
child: ListTile(
isThreeLine: item.status.delay != 0,
title: Text.rich(
trainIdSpan(rank: item.train.rank, number: item.train.number),
),
subtitle: Text.rich(
TextSpan(
children: [
TextSpan(
text: item.status.cancelled
? (arrival ? ViewStationPageShared.cancelledArrival : ViewStationPageShared.cancelledDeparture)
: item.time.add(Duration(minutes: max(0, item.status.delay))).compareTo(DateTime.now()) < 0
? (arrival ? ViewStationPageShared.arrivedFrom : ViewStationPageShared.departedTo)
: (arrival ? ViewStationPageShared.arrivesFrom : ViewStationPageShared.departsTo)
),
const TextSpan(text: ' '),
TextSpan(text: item.train.terminus),
if (item.status.delay != 0) ...[
const TextSpan(text: '\n'),
if (item.status.delay.abs() >= 60) ...[
TextSpan(text: (item.status.delay.abs() ~/ 60).toString()),
TextSpan(text: item.status.delay.abs() >= 120 ? ' ore' : ' oră'),
if (item.status.delay.abs() % 60 != 0)
const TextSpan(text: ' și '),
],
TextSpan(text: (item.status.delay.abs() % 60).toString()),
TextSpan(text: item.status.delay.abs() > 1 ? ' minute' : ' minut'),
const TextSpan(text: ' '),
if (item.status.delay > 0)
const TextSpan(
text: 'întârziere',
style: TextStyle(
inherit: true,
color: Colors.red,
),
)
else
const TextSpan(
text: 'mai devreme',
style: TextStyle(
inherit: true,
color: Colors.green,
),
),
],
],
),
),
),
), ),
), ),
if (item.status.platform != null) TextSpan(text: ' '),
IntrinsicHeight( TextSpan(text: item.train.number,),
child: AspectRatio(
aspectRatio: 1,
child: Badge(
text: item.status.platform!,
caption: 'Linia',
isOnTime: item.status.real && item.status.delay <= 0,
isDelayed: item.status.real && item.status.delay > 0,
),
),
),
], ],
), ),
), ),
subtitle: Text.rich(
TextSpan(
children: [
TextSpan(text: ViewStationPageState.arrivesFrom),
TextSpan(text: ' '),
TextSpan(text: item.train.origin),
],
),
),
onTap: () => onTrainTapped(item.train.number),
); );
} }
@override @override
Widget buildStationArrivalItem(BuildContext context, StationArrDep item) { Widget buildStationDepartureItem(BuildContext context, StationDeparture item) {
return buildStationItem(context, item, arrival: true); return ListTile(
} leading: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'),
title: Text.rich(
@override TextSpan(
Widget buildStationDepartureItem(BuildContext context, StationArrDep item) { children: [
return buildStationItem(context, item, arrival: false); TextSpan(
text: item.train.rank,
style: TextStyle(
color: item.train.rank.startsWith('IR') ? Color.fromARGB(255, 255, 0, 0) : null,
),
),
TextSpan(text: ' '),
TextSpan(text: item.train.number,),
],
),
),
subtitle: Text.rich(
TextSpan(
children: [
TextSpan(text: ViewStationPageState.departsTo),
TextSpan(text: ' '),
TextSpan(text: item.train.destination),
],
),
),
onTap: () => onTrainTapped(item.train.number),
);
} }
} }

104
lib/pages/train_info_page/select_train/select_train.dart

@ -1,110 +1,50 @@
import 'dart:io' show Platform;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/models.dart'; import 'package:info_tren/models/ui_design.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train_cupertino.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train_cupertino.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train_fluent.dart';
import 'package:info_tren/pages/train_info_page/select_train/select_train_material.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train_material.dart';
import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info.dart';
import 'package:info_tren/providers.dart'; import 'package:info_tren/utils/default_ui_design.dart';
import 'package:info_tren/api/trains.dart' as api_trains; import 'package:tuple/tuple.dart';
typedef TrainSelectedCallback = Function(int trainNumber); typedef TrainSelectedCallback(int trainNumber);
class SelectTrainPage extends ConsumerWidget { class SelectTrainPage extends StatefulWidget {
const SelectTrainPage({super.key}); final UiDesign? uiDesign;
SelectTrainPage({Key? key, this.uiDesign}) : super(key: key);
static String routeName = "/trainInfo/selectTrain"; static String routeName = "/trainInfo/selectTrain";
void onTrainSelected(BuildContext context, String selection) {
Navigator.of(context).pushNamed(TrainInfo.routeName, arguments: selection);
}
@override @override
Widget build(BuildContext context, WidgetRef ref) { SelectTrainPageState createState() {
final uiDesign = ref.watch(uiDesignProvider); final uiDesign = this.uiDesign ?? defaultUiDesign;
switch(uiDesign) { switch(uiDesign) {
case UiDesign.MATERIAL: case UiDesign.MATERIAL:
return const SelectTrainPageMaterial(); return SelectTrainPageStateMaterial();
case UiDesign.CUPERTINO: case UiDesign.CUPERTINO:
return const SelectTrainPageCupertino(); return SelectTrainPageStateCupertino();
case UiDesign.FLUENT:
return const SelectTrainPageFluent();
default: default:
throw UnmatchedUiDesignException(uiDesign); throw UnmatchedUiDesignException(uiDesign);
} }
} }
} }
abstract class SelectTrainPageShared extends StatefulWidget { abstract class SelectTrainPageState extends State<SelectTrainPage> {
const SelectTrainPageShared({super.key});
void onTrainSelected(BuildContext context, String selection) {
selection = selection.characters.takeWhile((char) => List.generate(10, (i) => i.toString()).contains(char)).join();
Navigator.of(context).pushNamed(
TrainInfo.routeName,
arguments: TrainInfoArguments(trainNumber: selection),
);
}
}
abstract class SelectTrainPageState extends State<SelectTrainPageShared> {
final String pageTitle = 'Informații despre tren'; final String pageTitle = 'Informații despre tren';
final String textFieldLabel = 'Numărul trenului'; final String textFieldLabel = 'Numărul trenului';
final trainNoController = TextEditingController(); TextEditingController trainNoController = TextEditingController();
List<TrainsResult> trains = [];
List<TrainsResult> get filteredTrains {
final filter = trainNoController.text
.trim()
.toLowerCase();
if (filter.isEmpty) {
return trains;
}
final filtered = trains.where((e) => e.number.contains(filter)).toList(growable: false);
filtered.sort((t1, t2) {
final posInNum1 = t1.number.indexOf(filter);
final posInNum2 = t2.number.indexOf(filter);
if (posInNum1 != posInNum2) {
return posInNum1.compareTo(posInNum2);
}
if (t1.number.length != t2.number.length) {
return t1.number.length.compareTo(t2.number.length);
}
return t1.number.compareTo(t2.number);
});
return filtered;
}
@override @override
void initState() { void initState() {
api_trains.trains.then((value) {
setState(() {
trains = value;
trains.sort((t1, t2) {
if (t1.company != t2.company) {
return t1.company.compareTo(t2.company);
}
if (t1.rank != t2.rank) {
return t1.rank.compareTo(t2.rank);
}
final t1Num = t1.number.padLeft(t2.number.length, '0');
final t2Num = t2.number.padLeft(t1.number.length, '0');
return t1Num.compareTo(t2Num);
});
});
});
super.initState(); super.initState();
} }
@ -113,9 +53,9 @@ abstract class SelectTrainPageState extends State<SelectTrainPageShared> {
} }
Widget get suggestionsList => SelectTrainSuggestions( Widget get suggestionsList => SelectTrainSuggestions(
choices: filteredTrains, uiDesign: widget.uiDesign,
userInput: trainNoController.text,
onTrainSelected: (trainNumber) => widget.onTrainSelected(context, trainNumber), onTrainSelected: (trainNumber) => widget.onTrainSelected(context, trainNumber),
currentInput: trainNoController.text,
key: ValueKey(trainNoController.text), key: ValueKey(trainNoController.text),
); );
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save