This commit is contained in:
Nedifinita
2025-08-29 13:28:14 +08:00
commit 25f66000cb
148 changed files with 10964 additions and 0 deletions

167
.gitignore vendored Normal file
View File

@@ -0,0 +1,167 @@
*.iml
.gradle
.kotlin
.idea/caches
.idea/libraries
.idea/modules.xml
.idea/workspace.xml
.idea/navEditor.xml
.idea/assetWizardSettings.xml
.DS_Store
build
captures
.externalNativeBuild
.cxx
local.properties
*.ps1
.*.bat
*.jks
*.keystore
*.base64
docs
linux
windows
flutter/ephemeral/
*.suo
*.user
*.userosscache
*.sln.docstates
x64/
x86/
*.[Cc]ache
!*.[Cc]ache/
.gradle/
build/
local.properties
*.log
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
*.jks
*.keystore
google-services.json
*.hprof
gen-external-apklibs
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.fvm/flutter_sdk
.packages
.pub-cache/
.pub/
coverage/
lib/generated_plugin_registrant.dart
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/key.properties
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
*.ap_
*.aab
*.dex
*.class
bin/
gen/
out/
.gradle
.signing/
proguard/
/*/build/
/*/local.properties
/*/out
/*/*/build
/*/*/production
.navigation/
*.ipr
*~
*.swp
.externalNativeBuild
obj/
*.iws
/out/
.idea/caches/
.idea/libraries/
.idea/shelf/
.idea/workspace.xml
.idea/tasks.xml
.idea/.name
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/encodings.xml
.idea/misc.xml
.idea/modules.xml
.idea/scopes/scope_settings.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
.idea/datasources.xml
.idea/dataSources.ids
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
.idea/assetWizardSettings.xml
.idea/gradle.xml
.idea/jarRepositories.xml
.idea/navEditor.xml
.classpath
.project
.cproject
.settings/
.mtj.tmp/
*.war
*.ear
hs_err_pid*
.idea_modules/
atlassian-ide-plugin.xml
.idea/mongoSettings.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
!/gradle/wrapper/gradle-wrapper.jar
macos/Flutter/ephemeral/flutter_export_environment.sh
macos/Flutter/ephemeral/Flutter-Generated.xcconfig
*.py

45
.metadata Normal file
View File

@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: android
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: ios
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: linux
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: macos
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: web
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: windows
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
# 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'

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# LBJ_Console
这是 LBJ_Console 的 Flutter 实现。

28
analysis_options.yaml Normal file
View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

83
android/app/build.gradle Normal file
View File

@@ -0,0 +1,83 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace = "org.noxylva.lbjconsole.flutter"
compileSdk = 35
ndkVersion = "25.1.8937393"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
coreLibraryDesugaringEnabled true
}
kotlinOptions {
jvmTarget = "11"
}
defaultConfig {
applicationId = "org.noxylva.lbjconsole.flutter"
minSdk = 29
targetSdk = 35
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
signingConfigs {
release {
storeFile file("../../keystore.jks")
storePassword System.getenv("KEYSTORE_PASSWORD") ?: "android"
keyAlias System.getenv("KEY_ALIAS") ?: "androidkey"
keyPassword System.getenv("KEY_PASSWORD") ?: "android"
}
}
buildTypes {
release {
signingConfig = signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
splits {
abi {
enable true
reset()
include "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
universalApk true
}
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,62 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<application
android:label="lbjconsole"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package org.noxylva.lbjconsole.flutter
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

19
android/build.gradle Normal file
View File

@@ -0,0 +1,19 @@
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/central' }
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/app/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/app/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/app/src/main/assets" />
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/app/src/main/libs" />
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/app/src/main/proguard_logs" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/app/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/app/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
</content>
<orderEntry type="jdk" jdkName="Android API 29 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Flutter for Android" level="project" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>

25
android/settings.gradle Normal file
View File

@@ -0,0 +1,25 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.2.2" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
}
include ":app"

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

430
assets/loco_info.csv Normal file
View File

@@ -0,0 +1,430 @@
6G,51,90,西安铁路局 宝鸡电力机务段,,
6K,1,85,郑州铁路局 洛阳机务段,,
8G,1,1,太原铁路局 太原北机务段、侯马机务段、石家庄电力机务段,,
8G,2,2,中国铁道博物馆,,
8G,3,75,太原铁路局 太原北机务段、侯马机务段、石家庄电力机务段,,
8G,76,76,太原铁路局 太原机务段北场,,
8G,77,96,太原铁路局 太原北机务段、侯马机务段、石家庄电力机务段,,
8G,97,97,太原铁路局 榆次机务折返段,,
8G,98,100,太原铁路局 太原北机务段、侯马机务段、石家庄电力机务段,,
8K,1,1,北京铁路局 丰台机务段,,
8K,2,7,*北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段,,
8K,8,8,中国铁道博物馆,*科技号,
8K,9,17,*北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段,,
8K,18,18,*北京铁路局 丰台机务段,,
8K,19,23,*北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段,,
8K,24,24,太原铁路局 湖东机务段 大同西运用车间,,
8K,25,64,*北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段,,
8K,65,65,天津铁道职业技术学院,,
8K,66,71,*北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段,,
8K,72,72,北京铁路局 丰台机务段,,
8K,73,90,*北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段,,
8K,91,91,太原铁路局 太原机务段北场 机车展场,,
8K,92,100,*北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段,,
DJ1,1,1,中国铁道科学研究院 环形铁道,,
DJ1,2,2,株洲西门子牵引设备有限公司,,
DJ1,3,3,西安铁路局 宝鸡机务段 秦岭附加队 ,,
DJ2,1,1,郑州铁路局 郑州机务段京武快车队,奥星,
DJ2,2,3,郑州铁路局 郑州机务段,奥星,
HXD1D,1,15,武汉铁路局集团有限公司 武昌南机务段,,
HXD1D,16,16,上海铁路局集团有限公司 杭州机务段,,
HXD1D,17,17,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,18,18,南昌铁路局集团有限公司 鹰潭机务段,,
HXD1D,19,19,上海铁路局集团有限公司 杭州机务段,,
HXD1D,20,20,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,21,21,上海铁路局集团有限公司 杭州机务段,,
HXD1D,22,24,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,25,25,南昌铁路局集团有限公司 鹰潭机务段,,
HXD1D,26,26,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,27,27,上海铁路局集团有限公司 杭州机务段,,
HXD1D,28,28,南昌铁路局集团有限公司 鹰潭机务段,,
HXD1D,29,34,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,35,35,上海铁路局集团有限公司 杭州机务段,,
HXD1D,36,38,兰州铁路局集团有限公司 兰州西机务段,,
HXD1D,39,39,中国铁路济南局集团有限公司 济南机务段,,
HXD1D,40,50,兰州铁路局集团有限公司 兰州西机务段,,
HXD1D,51,75,乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段,,
HXD1D,76,105,兰州铁路局集团有限公司 兰州西机务段,,
HXD1D,106,137,上海铁路局集团有限公司 上海机务段,,
HXD1D,138,168,上海铁路局集团有限公司 杭州机务段,,
HXD1D,169,175,上海铁路局集团有限公司 上海机务段,,
HXD1D,176,185,武汉铁路局集团有限公司 武昌南机务段,,
HXD1D,186,187,南昌铁路局集团有限公司 鹰潭机务段,,
HXD1D,188,188,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,189,190,南昌铁路局集团有限公司 鹰潭机务段,,
HXD1D,191,232,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,233,233,南昌铁路局集团有限公司 鹰潭机务段,,
HXD1D,234,237,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,238,257,广州铁路(集团)公司 广州机务段,,
HXD1D,258,270,武汉铁路局集团有限公司 武昌南机务段,,
HXD1D,271,275,乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段,,
HXD1D,276,279,上海铁路局集团有限公司 上海机务段,,
HXD1D,280,289,上海铁路局集团有限公司 徐州机务段,,
HXD1D,290,291,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,292,293,南昌铁路局集团有限公司 鹰潭机务段,,
HXD1D,294,295,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,296,300,广州铁路(集团)公司 广州机务段,,
HXD1D,301,310,武汉铁路局集团有限公司 武昌南机务段,,
HXD1D,311,320,乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段,,
HXD1D,321,340,青藏铁路集团有限公司 西宁机务段,,
HXD1D,341,362,乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段,,
HXD1D,363,382,广州铁路(集团)公司 广州机务段,,
HXD1D,383,392,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,393,405,上海铁路局集团有限公司 上海机务段,,
HXD1D,406,415,兰州铁路局集团有限公司 兰州西机务段,,
HXD1D,416,430,广州铁路(集团)公司 长沙机务段,,
HXD1D,431,440,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,441,445,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,446,450,乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段,,
HXD1D,451,460,武汉铁路局集团有限公司 武昌南机务段,,
HXD1D,461,470,广州铁路(集团)公司 广州机务段,,
HXD1D,471,478,兰州铁路局集团有限公司 兰州西机务段,,
HXD1D,479,483,上海铁路局集团有限公司 上海机务段,,
HXD1D,484,488,上海铁路局集团有限公司 杭州机务段,,
HXD1D,489,490,上海铁路局集团有限公司 杭州机务段,,
HXD1D,491,510,郑州铁路局集团有限公司 郑州机务段,,
HXD1D,511,512,上海铁路局集团有限公司 徐州机务段,,
HXD1D,513,515,上海铁路局集团有限公司 上海机务段,,
HXD1D,516,520,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,521,534,广州铁路(集团)公司 广州机务段,,
HXD1D,522,522,广州铁路职业技术学院,,
HXD1D,535,544,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,545,551,上海铁路局集团有限公司 上海机务段,,
HXD1D,552,554,上海铁路局集团有限公司 杭州机务段,,
HXD1D,555,559,郑州铁路局集团有限公司 郑州机务段,,
HXD1D,560,564,乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段,,
HXD1D,565,570,广州铁路(集团)公司 广州机务段,,
HXD1D,571,585,南昌铁路局集团有限公司 南昌机务段,,
HXD1D,586,595,上海铁路局集团有限公司 杭州机务段,,
HXD1D,596,613,兰州铁路局集团有限公司 兰州西机务段,,
HXD1D,614,623,广州铁路(集团)公司 广州机务段,,
HXD1D,624,633,上海铁路局集团有限公司 上海机务段,,
HXD1D,634,636,乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段,,
HXD1D,637,644,郑州铁路局集团有限公司 郑州机务段,,
HXD1D,645,660,郑州铁路局集团有限公司 郑州机务段,,
HXD1D,661,668,武汉铁路局集团有限公司 武昌南机务段,,
HXD1D,669,673,乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段,,
HXD1D,674,678,郑州铁路局集团有限公司 郑州机务段,,
HXD1D,679,682,上海铁路局集团有限公司 上海机务段,,
HXD1D,683,683,上海铁路局集团有限公司 徐州机务段,,
HXD1D,684,684,上海铁路局集团有限公司 徐州机务段,,
HXD1D,685,689,青藏铁路集团有限公司 格尔木机务段,,
HXD1D,1898,1898,上海铁路局集团有限公司 上海机务段,周恩来号,
HXD1D-J,1,3,青藏铁路集团有限公司 拉萨动车运用所,,
HXD1D-J,1001,1009,昆明铁路局集团有限公司 昆明动车运用所,,
HXD1D-J,1010,1013,青藏铁路集团有限公司 格尔木机务段,,
HXD1D-J,1014,1019,成都铁路局集团有限公司 成都动车运用所,,
HXD1D-J,1020,1027,昆明铁路局集团有限公司 昆明动车运用所,,
HXD3C,446,446,广州铁路(集团)公司 广州机务段,,
HXD3C,805,809,广州铁路(集团)公司 ,,
HXD3C,810,819,中国铁路南宁局集团有限公司,,
HXD3C,820,829,中国铁路武汉局集团有限公司,,
HXD3C,896,925,中国铁路沈阳局集团有限公司,,
HXD3C,926,930,中国铁路南宁局集团有限公司,,
HXD3C,931,945,中国铁路北京局集团有限公司,,
HXD3C,946,955,中国铁路济南局集团有限公司,,
HXD3C,956,965,中国铁路郑州局集团有限公司,,
HXD3C,966,974,中国铁路济南局集团有限公司,,
HXD3D,1,10,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,11,25,西安铁路局集团有限公司 西安机务段,,
HXD3D,26,34,兰州铁路局集团有限公司 兰州西机务段,,
HXD3D,35,35,兰州铁路局集团有限公司 迎水桥机务段,雷锋号,
HXD3D,36,38,兰州铁路局集团有限公司 兰州西机务段,,
HXD3D,39,39,济南铁路局集团有限公司 济南机务段,共青团号,
HXD3D,40,40,兰州铁路局集团有限公司 兰州西机务段,,
HXD3D,41,50,西安铁路局集团有限公司 西安机务段,,
HXD3D,51,70,北京铁路局集团有限公司 北京机务段,,
HXD3D,71,90,南昌铁路局集团有限公司 南昌机务段,,
HXD3D,91,115,兰州铁路局集团有限公司 兰州西机务段,,
HXD3D,116,135,昆明铁路局集团有限公司 昆明机务段,,
HXD3D,136,145,北京铁路局集团有限公司 北京机务段,,
HXD3D,146,150,呼和浩特铁路局集团有限公司 集宁机务段,,
HXD3D,151,155,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,156,160,南昌铁路局集团有限公司 南昌机务段,,
HXD3D,161,165,北京铁路局集团有限公司 北京机务段,,
HXD3D,166,170,西安铁路局集团有限公司 西安机务段,,
HXD3D,171,180,兰州铁路局集团有限公司 兰州西机务段,,
HXD3D,181,190,济南铁路局集团有限公司 济南机务段,,
HXD3D,191,245,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,246,255,北京铁路局集团有限公司 北京机务段,,
HXD3D,256,265,呼和浩特铁路局集团有限公司 集宁机务段,,
HXD3D,266,290,兰州铁路局集团有限公司 兰州西机务段,,
HXD3D,291,300,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,301,310,济南铁路局集团有限公司 济南机务段,,
HXD3D,310,315,昆明铁路局集团有限公司 昆明机务段,,
HXD3D,316,320,南昌铁路局集团有限公司 南昌机务段,,
HXD3D,321,322,呼和浩特铁路局集团有限公司 集宁机务段,,
HXD3D,323,325,北京铁路局集团有限公司 北京机务段,,
HXD3D,326,333,西安铁路局集团有限公司 西安机务段,,
HXD3D,334,340,西安铁路局集团有限公司 安康机务段,,
HXD3D,341,345,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,346,346,成都铁路局集团有限公司 重庆机务段,,
HXD3D,351,351,成都铁路局集团有限公司 重庆机务段,,
HXD3D,356,365,西安铁路局集团有限公司 安康机务段,,
HXD3D,366,369,北京铁路局集团有限公司 北京机务段,,
HXD3D,370,382,呼和浩特铁路局集团有限公司 集宁机务段,,
HXD3D,383,392,昆明铁路局集团有限公司 昆明机务段,,
HXD3D,393,397,兰州铁路局集团有限公司 兰州西机务段,,
HXD3D,398,402,西安铁路局集团有限公司 西安机务段,,
HXD3D,403,417,西安铁路局集团有限公司 西安机务段,,
HXD3D,418,419,哈尔滨铁路局集团有限公司 牡丹江机务段,,
HXD3D,420,424,北京铁路局集团有限公司 北京机务段,,
HXD3D,425,429,呼和浩特铁路局集团有限公司 集宁机务段,,
HXD3D,430,433,哈尔滨铁路局集团有限公司 牡丹江机务段,,
HXD3D,434,443,南昌铁路局集团有限公司 南昌机务段,,
HXD3D,444,449,济南铁路局集团有限公司 济南机务段,,
HXD3D,450,464,西安铁路局集团有限公司 西安机务段,,
HXD3D,465,468,济南铁路局集团有限公司 济南机务段,,
HXD3D,469,473,济南铁路局集团有限公司 济南机务段,,
HXD3D,474,479,昆明铁路局集团有限公司 昆明机务段,,
HXD3D,480,484,南昌铁路局集团有限公司 南昌机务段,,
HXD3D,485,489,西安铁路局集团有限公司 西安机务段,,
HXD3D,490,499,北京铁路局集团有限公司 北京机务段,,
HXD3D,500,503,哈尔滨铁路局集团有限公司 牡丹江机务段,,
HXD3D,504,514,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,515,515,成都铁路局集团有限公司 重庆机务段,,
HXD3D,516,518,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,519,528,西安铁路局集团有限公司 西安机务段,,
HXD3D,529,538,北京铁路局集团有限公司 北京机务段,,
HXD3D,539,541,呼和浩特铁路局集团有限公司 集宁机务段,,
HXD3D,542,553,西安铁路局集团有限公司 西安机务段,,
HXD3D,554,563,济南铁路局集团有限公司 济南机务段,,
HXD3D,564,568,昆明铁路局集团有限公司 昆明机务段,,
HXD3D,569,573,成都铁路局集团有限公司 重庆机务段,,
HXD3D,574,583,济南铁路局集团有限公司 济南机务段,,
HXD3D,584,584,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,585,609,哈尔滨铁路局集团有限公司 三棵树机务段,,
HXD3D,610,611,北京铁路局集团有限公司 北京机务段,,
HXD3D,612,621,南昌铁路局集团有限公司 南昌机务段,,
HXD3D,622,626,南昌铁路局集团有限公司 南昌机务段,,
HXD3D,627,629,哈尔滨铁路局集团有限公司 三棵树机务段,,
HXD3D,630,630,哈尔滨铁路局集团有限公司 哈尔滨机务段,,
HXD3D,631,631,西安铁路局集团有限公司 西安机务段,第五代“钢人铁马号”,
HXD3D,632,653,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,654,673,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,674,681,沈阳铁路局集团有限公司 沈阳机务段,,
HXD3D,682,688,兰州铁路局集团有限公司 兰州西机务段,,
HXD3D,1886,1886,哈尔滨铁路局集团有限公司 哈尔滨机务段,第五代“朱德号”,
HXD3D,1893,1893,北京铁路局集团有限公司 丰台机务段,第六代“毛泽东号”,
HXD3D,1921,1921,沈阳铁路局集团有限公司 沈阳机务段,共产党员号,
HXD3D,7001,7002,广西沿海铁路股份有限公司 南宁南机务运用段,,
HXD3D,7003,7003,吉林铁道职业技术学院,,
HXD3D,8001,8025,沈阳铁路局集团有限公司 沈阳机务段,,大同
HXD3D,8026,8028,太原铁路局集团有限公司 太原南机务段,,大同
东方红2,1,50,,,资阳
东风,1201,1830,,,大连、成都
东风,2001,2094,,,戚墅堰
东风11,1,459,,,戚墅堰
东风12,8001,8001,吉林铁道职业技术学院,,
东风2,3201,3348,,,戚墅堰
东风21,1,5,中国铁路昆明局集团有限公司 昆明机务段,,
东风21,6,6,中国铁路昆明局集团有限公司 昆明机务段,状元号,
东风21,7,7,中国铁路昆明局集团有限公司 昆明机务段,亲年号,
东风21,8,8,中国铁路昆明局集团有限公司 昆明机务段,建水古城,
东风21,9,100,中国铁路昆明局集团有限公司 昆明机务段,,
东风21,101,101,中国铁路昆明局集团有限公司 昆明机务段,异龙号,
东风21,102,102,中国铁路昆明局集团有限公司 昆明机务段,,
东风21,1001,1002,云南钢铁厂,,
东风2Z,3251,3251,*齐齐哈尔铁路局 加格达奇机务段,,
东风3,3243,3243,中车共享城机车公园,,
东风4,3247,3247,中车成都轨道交通产业园,,
东风4B,1001,1999,,,大连
东风4B,1963,1963,*北京铁路局 丰台机务段,,
东风4B,2101,2685,,,大连
东风4B,2104,2104,*上海铁路局 蚌埠机务段,,
东风4B,2376,2376,*南昌铁路局 鹰潭机务段,,
东风4B,3101,3999,,,资阳
东风4B,3214,3214,*浙江金温铁道开发有限公司 温州机务段,,
东风4B,3249,3249,*西安铁路局 西安机务段,,
东风4B,3390,3390,*成都铁路局 重庆机务段,,
东风4B,3593,3593,*广州铁路(集团)公司 株洲机务段,,
东风4B,6001,6587,,,大同
东风4B,6530,6530,*南宁铁路局 南宁机务段,,
东风4B,7001,7363,,,大连
东风4B,7364,7365,,,四方
东风4B,7366,7796,,,大连
东风4B,7701,7732,,,戚墅堰改
东风4B,9001,9702,,,资阳
东风4B,9167,9167,*南昌铁路局 向塘机务段,,
东风4B,9531,9531,*新长铁路公司,,
东风4C,1,10,,,大同
东风4C,11,11,北京铁路局 丰台段,青年文明号,
东风4C,12,40,,,大同
东风4C,2001,2006,,,四方
东风4C,4001,4465,,,大连
东风4C,4466,4466,四方机车车辆厂,,四方
东风4C,5001,5273,,,资阳
东风4C,5274,5275,三茂铁路公司 三水机务段,东风4CK,
东风4C,5276,5335,,,资阳
东风4D,7001,7021,中国铁路南宁局集团有限公司,,
东风5,1,1,中国铁路北京局集团有限公司 北京车辆段,,
东风5,1974,1975,中国铁路兰州局集团有限公司 兰州西机务段,,唐山
东风5,1976,2082,,,唐山
东风5,2083,2083,中国石油兰州石化公司,,唐山
东风5,3279,3279,云南铁路博物馆,,
东风6,1,2,*沈阳铁路局 大连机务段,,
东风6,3,3,沈阳铁路陈列馆,,
东风6,4,4,*沈阳铁路局 大连机务段,,
东风7,174,174,太原机务段北场,,
东风7B,3006,3006,中国铁道博物馆,,
东风7B,3015,3015,王坪村铁路公园,,
东风7B,6001,6072,*北京铁路局 邯郸机务段;郑州铁路局 新乡机务段,调车,
东风7D,1,1,中国铁道博物馆,,
东风7D,3001,3001,中国铁道博物馆,,
东风7E,1,1,郑州铁路局 新乡机务段,,
东风7E,2,2,郑州铁路局 郑州机务段,,
东风7G,9001,9004,呼和浩特铁路局 集宁机务段 赛汗塔拉分段,,
东风8,1,1,中国铁道博物馆,,
东风9,1,2,中国铁路广州局集团有限公司广州机务段,,
韶山1,8,8,中国铁道博物馆,,
韶山1,156,156,郑州世纪欢乐园,,
韶山1,160,160,北京铁路电气化学校,,
韶山1,227,227,兰州铁路局 兰州西机务段,,
韶山1,254,254,北京铁路局 丰台机务段 储备厂,,
韶山1,307,307,太原铁路局 榆次机务折返段,,
韶山1,309,309,太原铁路局 太原机务段北场,,
韶山1,321,321,武汉铁路职业技术学院,,
韶山1,681,681,中国铁道博物馆,,
韶山1,695,695,沈阳铁路陈列馆,,
韶山1,762,762,广州铁路(集团)公司 娄底运用车间储备厂,,
韶山1,818,818,西南交通大学 机车博物园,,
韶山1,821,821,韶关机务实训基地,,
韶山1,826,826,韶关机务实训基地,,
韶山3,454,454,成都铁路局 贵阳机务段,先锋号,
韶山3,524,524,武汉铁路局 江岸机务段,青年号,
韶山3,4160,4160,广西沿海铁路公司 南宁南机务运用段,共青团号,
韶山3,4178,4178,广西沿海铁路公司 南宁南机务运用段,共青团号,
韶山3,4235,4235,成都铁路局 重庆机务段,青年文明号,
韶山3,4258,4258,成都铁路局 重庆机务段,党员先锋号,
韶山3,5080,5080,广铁机车博物馆,,
韶山3,6005,6005,湖南交通工程学院,,
韶山3,8050,8050,武汉四美塘铁路遗址公园,,
韶山3B,16,16,西安铁路局 安康机务段,青年文明号,
韶山3B,5001,5001,成都铁路局 贵阳机务段,*先锋力神,
韶山3B,5035,5035,兰州铁路局 迎水桥机务段,雷锋号 (曾),
韶山3B,5038,5038,兰州铁路局 迎水桥机务段,青年文明号,
韶山3B,5151,5151,成都铁路局 西昌机务段,扶贫先锋号,
韶山3B,5162,5162,昆明铁路局 昆明机务段,五四青年号,
韶山3B,5235,5235,成都铁路局 西昌机务段,*共青团号,
韶山3C,1,1,贵阳机务段,,
韶山4,6,6,中国铁道博物馆,,
韶山4,10,10,成都铁路局 西昌机务段,,
韶山4,50,50,郑州铁路局 新乡机务段,先锋号,
韶山4,63,63,太原铁路局 太原机务段,,
韶山4,204,204,郑州铁路局 新乡机务段,先锋号,
韶山4,448,448,沈阳铁路局 苏家屯机务段,先锋号,
韶山4,574,574,中铁三局集团,先锋号,
韶山4,743,743,哈尔滨铁路局 哈尔滨机务段,青年文明号,
韶山4,855,855,西安铁路局 新丰镇机务段,,
韶山4,911,911,中铁三局集团,青年文明号,
韶山4,2006,2006,吉林铁道职业技术学院,,
韶山4B,19,19,神朔铁路公司 神木北机务段,青年号,
韶山4B,89,89,神朔铁路公司 神木北机务段,青年文明号,
韶山4B,90,90,神朔铁路公司 神木北机务段,青年文明号,
韶山4B,257,257,包神铁路公司 东胜机务段,党员先锋号,
韶山4G,159,1177,,,株洲
韶山4G,168,168,中国铁道博物馆,,
韶山4G,171,171,哈尔滨铁路局 牡丹江机务段,,
韶山4G,179,179,太原铁路局 湖东机务段,,
韶山4G,466,466,石家庄铁道大学,,
韶山4G,1089,1089,*呼和浩特铁路局 包头西机务段,,
韶山4G,1886,1886,哈尔滨铁路局 哈尔滨机务段,*朱德号,株洲
韶山4G,3001,3002,,,资阳
韶山4G,6001,6001,中国铁道博物馆,,
韶山4G,6001,6001,中国铁道博物馆,,大同
韶山4G,7001,7110,,,大连
韶山4G,7121,7243,,,大连
韶山5,1,1,中国铁道博物馆,,
韶山5,2,2,郑州世纪欢乐园 ,,
韶山6,1,1,郑州铁路司机学校,,
韶山6,2,2,中国铁道博物馆,,
韶山6B,1011,1011,西安铁路局 西安机务段,*青年文明号,
韶山6B,1026,1026,韶关机务实训基地,,
韶山6B,1088,1088,武汉铁路局 襄阳机务段,*民兵号,
韶山6B,1111,1111,武汉铁路局 襄阳机务段,*先锋号,
韶山6B,6001,6001,韶关机务实训基地,,
韶山6B,6002,6002,广州铁路博物馆,,
韶山7,1,79,南宁铁路局集团有限公司 柳州机务段,,
韶山7,76,76,南宁铁路局集团有限公司 南宁机务段,*五四红旗号,
韶山7,80,84,南宁铁路局集团有限公司 柳州机务段,,
韶山7,85,111,南宁铁路局集团有限公司 柳州机务段,,
韶山7,8112,8113,山西孝柳铁路有限责任公司,,
韶山7B,1,1,*南宁铁路局集团有限公司 南宁机务段,,
韶山7B,2,2,中国铁路南宁局集团有限公司 柳州机务段,,
韶山7D,1,58,西安铁路局集团有限公司 西安机务段,,
韶山7D,631,631,西安铁路局集团有限公司 西安机务段,*钢人铁马号,
韶山7E,1,140,,,大同
韶山7E,6001,6002,昆明铁路局,,大同
韶山7E,7001,7004,,,大连
韶山8,1,1,中国铁路广州局集团有限公司 广州机务段,,
韶山8,2,2,中国铁路广州局集团有限公司 广州机务段,,
韶山8,3,4,中国铁路上海局集团有限公司 上海机务段,,
韶山8,5,5,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,9,9,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,11,11,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,12,12,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,15,16,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,17,17,中国铁路上海局集团有限公司 上海机务段,,
韶山8,20,20,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,24,25,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,27,27,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,29,32,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,33,35,中国铁路上海局集团有限公司 上海机务段,,
韶山8,36,36,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,38,38,中国铁路上海局集团有限公司 上海机务段,,
韶山8,39,39,中国铁路上海局集团有限公司 上海机务段,国祥号,
韶山8,40,40,中国铁路上海局集团有限公司 上海机务段,,
韶山8,41,41,中国铁路北京局集团有限公司 北京机务段,,
韶山8,43,43,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,44,44,中国铁路北京局集团有限公司 邯郸机务段,,
韶山8,45,45,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,48,48,中国铁路北京局集团有限公司 邯郸机务段,,
韶山8,49,49,中国铁路南昌局集团有限公司 南昌机务段,,
韶山8,50,50,中国铁路南昌局集团有限公司 南昌机务段,,
韶山8,51,51,中国铁路北京局集团有限公司 邯郸机务段,,
韶山8,52,52,中国铁路上海局集团有限公司 上海机务段,,
韶山8,55,55,中国铁路南昌局集团有限公司 南昌机务段,,
韶山8,56,57,中国铁路北京局集团有限公司 邯郸机务段,,
韶山8,64,64,中国铁路广州局集团有限公司 广州机务段,,
韶山8,72,72,中国铁路北京局集团有限公司 邯郸机务段,,
韶山8,73,73,中国铁路北京局集团有限公司 北京机务段,,
韶山8,74,74,中国铁路北京局集团有限公司 邯郸机务段,,
韶山8,81,81,中国铁路北京局集团有限公司 北京机务段,,
韶山8,83,84,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,85,85,中国铁路北京局集团有限公司 北京机务段,,
韶山8,88,103,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,104,104,中国铁路北京局集团有限公司 邯郸机务段,,
韶山8,109,111,中国铁路南昌局集团有限公司 南昌机务段,,
韶山8,114,116,中国铁路南昌局集团有限公司 南昌机务段,,
韶山8,118,119,中国铁路北京局集团有限公司 北京机务段,,
韶山8,121,126,中国铁路北京局集团有限公司 北京机务段,,
韶山8,127,128,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,130,130,中国铁路南昌局集团有限公司 南昌机务段,,
韶山8,131,131,中国铁路广州局集团有限公司 长沙机务段,,
韶山8,132,132,中国铁路广州局集团有限公司 长沙机务段,,
韶山8,133,133,中国铁路广州局集团有限公司 长沙机务段,,
韶山8,134,134,中国铁路广州局集团有限公司 长沙机务段,,
韶山8,136,136,中国铁路广州局集团有限公司 长沙机务段,,
韶山8,141,141,中国铁路广州局集团有限公司 广州机务段,,
韶山8,144,144,中国铁路广州局集团有限公司 长沙机务段,,
韶山8,148,148,中国铁路广州局集团有限公司 广州机务段,,
韶山8,156,156,中国铁路广州局集团有限公司 广州机务段,,
韶山8,163,163,中国铁路广州局集团有限公司 广州机务段,,
韶山8,166,166,中国铁路广州局集团有限公司 广州机务段,新世纪金龙号,
韶山8,171,171,中国铁路上海局集团有限公司 上海机务段,,
韶山8,172,172,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,173,173,中国铁路广州局集团有限公司 广州机务段,,
韶山8,181,181,中国铁路广州局集团有限公司 广州机务段,,
韶山8,186,186,中国铁路广州局集团有限公司 广州机务段,,
韶山8,191,191,中国铁路广州局集团有限公司 广州机务段,,
韶山8,192,192,中国铁路广州局集团有限公司 广州机务段,,
韶山8,197,197,中国铁路郑州局集团有限公司 郑州机务段,,
韶山8,200,204,中国铁路上海局集团有限公司 上海机务段,,
韶山8,205,205,中国铁路广州局集团有限公司 长沙机务段,,
韶山8,214,214,中国铁路郑州局集团有限公司 郑州机务段,,
韶山9,1,3,沈阳铁路局 沈阳机务段;上海铁路局 上海机务段,,
韶山9,5,29,沈阳铁路局 沈阳机务段;上海铁路局 上海机务段,,
韶山9,30,30,沈阳铁路局 通辽机务段,,
韶山9,31,37,沈阳铁路局 沈阳机务段;上海铁路局 上海机务段,,
韶山9,38,38,沈阳铁路局 通辽机务段,,
韶山9,39,43,沈阳铁路局 沈阳机务段;上海铁路局 上海机务段,,
1 6G 51 90 西安铁路局 宝鸡电力机务段
2 6K 1 85 郑州铁路局 洛阳机务段
3 8G 1 1 太原铁路局 太原北机务段、侯马机务段、石家庄电力机务段
4 8G 2 2 中国铁道博物馆
5 8G 3 75 太原铁路局 太原北机务段、侯马机务段、石家庄电力机务段
6 8G 76 76 太原铁路局 太原机务段北场
7 8G 77 96 太原铁路局 太原北机务段、侯马机务段、石家庄电力机务段
8 8G 97 97 太原铁路局 榆次机务折返段
9 8G 98 100 太原铁路局 太原北机务段、侯马机务段、石家庄电力机务段
10 8K 1 1 北京铁路局 丰台机务段
11 8K 2 7 *北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段
12 8K 8 8 中国铁道博物馆 *科技号
13 8K 9 17 *北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段
14 8K 18 18 *北京铁路局 丰台机务段
15 8K 19 23 *北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段
16 8K 24 24 太原铁路局 湖东机务段 大同西运用车间
17 8K 25 64 *北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段
18 8K 65 65 天津铁道职业技术学院
19 8K 66 71 *北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段
20 8K 72 72 北京铁路局 丰台机务段
21 8K 73 90 *北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段
22 8K 91 91 太原铁路局 太原机务段北场 机车展场
23 8K 92 100 *北京铁路局 丰台西段、丰台段;太原铁路局 大同西段、湖东段
24 DJ1 1 1 中国铁道科学研究院 环形铁道
25 DJ1 2 2 株洲西门子牵引设备有限公司
26 DJ1 3 3 西安铁路局 宝鸡机务段 秦岭附加队
27 DJ2 1 1 郑州铁路局 郑州机务段京武快车队 奥星
28 DJ2 2 3 郑州铁路局 郑州机务段 奥星
29 HXD1D 1 15 武汉铁路局集团有限公司 武昌南机务段
30 HXD1D 16 16 上海铁路局集团有限公司 杭州机务段
31 HXD1D 17 17 南昌铁路局集团有限公司 南昌机务段
32 HXD1D 18 18 南昌铁路局集团有限公司 鹰潭机务段
33 HXD1D 19 19 上海铁路局集团有限公司 杭州机务段
34 HXD1D 20 20 南昌铁路局集团有限公司 南昌机务段
35 HXD1D 21 21 上海铁路局集团有限公司 杭州机务段
36 HXD1D 22 24 南昌铁路局集团有限公司 南昌机务段
37 HXD1D 25 25 南昌铁路局集团有限公司 鹰潭机务段
38 HXD1D 26 26 南昌铁路局集团有限公司 南昌机务段
39 HXD1D 27 27 上海铁路局集团有限公司 杭州机务段
40 HXD1D 28 28 南昌铁路局集团有限公司 鹰潭机务段
41 HXD1D 29 34 南昌铁路局集团有限公司 南昌机务段
42 HXD1D 35 35 上海铁路局集团有限公司 杭州机务段
43 HXD1D 36 38 兰州铁路局集团有限公司 兰州西机务段
44 HXD1D 39 39 中国铁路济南局集团有限公司 济南机务段
45 HXD1D 40 50 兰州铁路局集团有限公司 兰州西机务段
46 HXD1D 51 75 乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段
47 HXD1D 76 105 兰州铁路局集团有限公司 兰州西机务段
48 HXD1D 106 137 上海铁路局集团有限公司 上海机务段
49 HXD1D 138 168 上海铁路局集团有限公司 杭州机务段
50 HXD1D 169 175 上海铁路局集团有限公司 上海机务段
51 HXD1D 176 185 武汉铁路局集团有限公司 武昌南机务段
52 HXD1D 186 187 南昌铁路局集团有限公司 鹰潭机务段
53 HXD1D 188 188 南昌铁路局集团有限公司 南昌机务段
54 HXD1D 189 190 南昌铁路局集团有限公司 鹰潭机务段
55 HXD1D 191 232 南昌铁路局集团有限公司 南昌机务段
56 HXD1D 233 233 南昌铁路局集团有限公司 鹰潭机务段
57 HXD1D 234 237 南昌铁路局集团有限公司 南昌机务段
58 HXD1D 238 257 广州铁路(集团)公司 广州机务段
59 HXD1D 258 270 武汉铁路局集团有限公司 武昌南机务段
60 HXD1D 271 275 乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段
61 HXD1D 276 279 上海铁路局集团有限公司 上海机务段
62 HXD1D 280 289 上海铁路局集团有限公司 徐州机务段
63 HXD1D 290 291 南昌铁路局集团有限公司 南昌机务段
64 HXD1D 292 293 南昌铁路局集团有限公司 鹰潭机务段
65 HXD1D 294 295 南昌铁路局集团有限公司 南昌机务段
66 HXD1D 296 300 广州铁路(集团)公司 广州机务段
67 HXD1D 301 310 武汉铁路局集团有限公司 武昌南机务段
68 HXD1D 311 320 乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段
69 HXD1D 321 340 青藏铁路集团有限公司 西宁机务段
70 HXD1D 341 362 乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段
71 HXD1D 363 382 广州铁路(集团)公司 广州机务段
72 HXD1D 383 392 南昌铁路局集团有限公司 南昌机务段
73 HXD1D 393 405 上海铁路局集团有限公司 上海机务段
74 HXD1D 406 415 兰州铁路局集团有限公司 兰州西机务段
75 HXD1D 416 430 广州铁路(集团)公司 长沙机务段
76 HXD1D 431 440 南昌铁路局集团有限公司 南昌机务段
77 HXD1D 441 445 南昌铁路局集团有限公司 南昌机务段
78 HXD1D 446 450 乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段
79 HXD1D 451 460 武汉铁路局集团有限公司 武昌南机务段
80 HXD1D 461 470 广州铁路(集团)公司 广州机务段
81 HXD1D 471 478 兰州铁路局集团有限公司 兰州西机务段
82 HXD1D 479 483 上海铁路局集团有限公司 上海机务段
83 HXD1D 484 488 上海铁路局集团有限公司 杭州机务段
84 HXD1D 489 490 上海铁路局集团有限公司 杭州机务段
85 HXD1D 491 510 郑州铁路局集团有限公司 郑州机务段
86 HXD1D 511 512 上海铁路局集团有限公司 徐州机务段
87 HXD1D 513 515 上海铁路局集团有限公司 上海机务段
88 HXD1D 516 520 南昌铁路局集团有限公司 南昌机务段
89 HXD1D 521 534 广州铁路(集团)公司 广州机务段
90 HXD1D 522 522 广州铁路职业技术学院
91 HXD1D 535 544 南昌铁路局集团有限公司 南昌机务段
92 HXD1D 545 551 上海铁路局集团有限公司 上海机务段
93 HXD1D 552 554 上海铁路局集团有限公司 杭州机务段
94 HXD1D 555 559 郑州铁路局集团有限公司 郑州机务段
95 HXD1D 560 564 乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段
96 HXD1D 565 570 广州铁路(集团)公司 广州机务段
97 HXD1D 571 585 南昌铁路局集团有限公司 南昌机务段
98 HXD1D 586 595 上海铁路局集团有限公司 杭州机务段
99 HXD1D 596 613 兰州铁路局集团有限公司 兰州西机务段
100 HXD1D 614 623 广州铁路(集团)公司 广州机务段
101 HXD1D 624 633 上海铁路局集团有限公司 上海机务段
102 HXD1D 634 636 乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段
103 HXD1D 637 644 郑州铁路局集团有限公司 郑州机务段
104 HXD1D 645 660 郑州铁路局集团有限公司 郑州机务段
105 HXD1D 661 668 武汉铁路局集团有限公司 武昌南机务段
106 HXD1D 669 673 乌鲁木齐铁路局集团有限公司 乌鲁木齐机务段
107 HXD1D 674 678 郑州铁路局集团有限公司 郑州机务段
108 HXD1D 679 682 上海铁路局集团有限公司 上海机务段
109 HXD1D 683 683 上海铁路局集团有限公司 徐州机务段
110 HXD1D 684 684 上海铁路局集团有限公司 徐州机务段
111 HXD1D 685 689 青藏铁路集团有限公司 格尔木机务段
112 HXD1D 1898 1898 上海铁路局集团有限公司 上海机务段 周恩来号
113 HXD1D-J 1 3 青藏铁路集团有限公司 拉萨动车运用所
114 HXD1D-J 1001 1009 昆明铁路局集团有限公司 昆明动车运用所
115 HXD1D-J 1010 1013 青藏铁路集团有限公司 格尔木机务段
116 HXD1D-J 1014 1019 成都铁路局集团有限公司 成都动车运用所
117 HXD1D-J 1020 1027 昆明铁路局集团有限公司 昆明动车运用所
118 HXD3C 446 446 广州铁路(集团)公司 广州机务段
119 HXD3C 805 809 广州铁路(集团)公司
120 HXD3C 810 819 中国铁路南宁局集团有限公司
121 HXD3C 820 829 中国铁路武汉局集团有限公司
122 HXD3C 896 925 中国铁路沈阳局集团有限公司
123 HXD3C 926 930 中国铁路南宁局集团有限公司
124 HXD3C 931 945 中国铁路北京局集团有限公司
125 HXD3C 946 955 中国铁路济南局集团有限公司
126 HXD3C 956 965 中国铁路郑州局集团有限公司
127 HXD3C 966 974 中国铁路济南局集团有限公司
128 HXD3D 1 10 沈阳铁路局集团有限公司 沈阳机务段
129 HXD3D 11 25 西安铁路局集团有限公司 西安机务段
130 HXD3D 26 34 兰州铁路局集团有限公司 兰州西机务段
131 HXD3D 35 35 兰州铁路局集团有限公司 迎水桥机务段 雷锋号
132 HXD3D 36 38 兰州铁路局集团有限公司 兰州西机务段
133 HXD3D 39 39 济南铁路局集团有限公司 济南机务段 共青团号
134 HXD3D 40 40 兰州铁路局集团有限公司 兰州西机务段
135 HXD3D 41 50 西安铁路局集团有限公司 西安机务段
136 HXD3D 51 70 北京铁路局集团有限公司 北京机务段
137 HXD3D 71 90 南昌铁路局集团有限公司 南昌机务段
138 HXD3D 91 115 兰州铁路局集团有限公司 兰州西机务段
139 HXD3D 116 135 昆明铁路局集团有限公司 昆明机务段
140 HXD3D 136 145 北京铁路局集团有限公司 北京机务段
141 HXD3D 146 150 呼和浩特铁路局集团有限公司 集宁机务段
142 HXD3D 151 155 沈阳铁路局集团有限公司 沈阳机务段
143 HXD3D 156 160 南昌铁路局集团有限公司 南昌机务段
144 HXD3D 161 165 北京铁路局集团有限公司 北京机务段
145 HXD3D 166 170 西安铁路局集团有限公司 西安机务段
146 HXD3D 171 180 兰州铁路局集团有限公司 兰州西机务段
147 HXD3D 181 190 济南铁路局集团有限公司 济南机务段
148 HXD3D 191 245 沈阳铁路局集团有限公司 沈阳机务段
149 HXD3D 246 255 北京铁路局集团有限公司 北京机务段
150 HXD3D 256 265 呼和浩特铁路局集团有限公司 集宁机务段
151 HXD3D 266 290 兰州铁路局集团有限公司 兰州西机务段
152 HXD3D 291 300 沈阳铁路局集团有限公司 沈阳机务段
153 HXD3D 301 310 济南铁路局集团有限公司 济南机务段
154 HXD3D 310 315 昆明铁路局集团有限公司 昆明机务段
155 HXD3D 316 320 南昌铁路局集团有限公司 南昌机务段
156 HXD3D 321 322 呼和浩特铁路局集团有限公司 集宁机务段
157 HXD3D 323 325 北京铁路局集团有限公司 北京机务段
158 HXD3D 326 333 西安铁路局集团有限公司 西安机务段
159 HXD3D 334 340 西安铁路局集团有限公司 安康机务段
160 HXD3D 341 345 沈阳铁路局集团有限公司 沈阳机务段
161 HXD3D 346 346 成都铁路局集团有限公司 重庆机务段
162 HXD3D 351 351 成都铁路局集团有限公司 重庆机务段
163 HXD3D 356 365 西安铁路局集团有限公司 安康机务段
164 HXD3D 366 369 北京铁路局集团有限公司 北京机务段
165 HXD3D 370 382 呼和浩特铁路局集团有限公司 集宁机务段
166 HXD3D 383 392 昆明铁路局集团有限公司 昆明机务段
167 HXD3D 393 397 兰州铁路局集团有限公司 兰州西机务段
168 HXD3D 398 402 西安铁路局集团有限公司 西安机务段
169 HXD3D 403 417 西安铁路局集团有限公司 西安机务段
170 HXD3D 418 419 哈尔滨铁路局集团有限公司 牡丹江机务段
171 HXD3D 420 424 北京铁路局集团有限公司 北京机务段
172 HXD3D 425 429 呼和浩特铁路局集团有限公司 集宁机务段
173 HXD3D 430 433 哈尔滨铁路局集团有限公司 牡丹江机务段
174 HXD3D 434 443 南昌铁路局集团有限公司 南昌机务段
175 HXD3D 444 449 济南铁路局集团有限公司 济南机务段
176 HXD3D 450 464 西安铁路局集团有限公司 西安机务段
177 HXD3D 465 468 济南铁路局集团有限公司 济南机务段
178 HXD3D 469 473 济南铁路局集团有限公司 济南机务段
179 HXD3D 474 479 昆明铁路局集团有限公司 昆明机务段
180 HXD3D 480 484 南昌铁路局集团有限公司 南昌机务段
181 HXD3D 485 489 西安铁路局集团有限公司 西安机务段
182 HXD3D 490 499 北京铁路局集团有限公司 北京机务段
183 HXD3D 500 503 哈尔滨铁路局集团有限公司 牡丹江机务段
184 HXD3D 504 514 沈阳铁路局集团有限公司 沈阳机务段
185 HXD3D 515 515 成都铁路局集团有限公司 重庆机务段
186 HXD3D 516 518 沈阳铁路局集团有限公司 沈阳机务段
187 HXD3D 519 528 西安铁路局集团有限公司 西安机务段
188 HXD3D 529 538 北京铁路局集团有限公司 北京机务段
189 HXD3D 539 541 呼和浩特铁路局集团有限公司 集宁机务段
190 HXD3D 542 553 西安铁路局集团有限公司 西安机务段
191 HXD3D 554 563 济南铁路局集团有限公司 济南机务段
192 HXD3D 564 568 昆明铁路局集团有限公司 昆明机务段
193 HXD3D 569 573 成都铁路局集团有限公司 重庆机务段
194 HXD3D 574 583 济南铁路局集团有限公司 济南机务段
195 HXD3D 584 584 沈阳铁路局集团有限公司 沈阳机务段
196 HXD3D 585 609 哈尔滨铁路局集团有限公司 三棵树机务段
197 HXD3D 610 611 北京铁路局集团有限公司 北京机务段
198 HXD3D 612 621 南昌铁路局集团有限公司 南昌机务段
199 HXD3D 622 626 南昌铁路局集团有限公司 南昌机务段
200 HXD3D 627 629 哈尔滨铁路局集团有限公司 三棵树机务段
201 HXD3D 630 630 哈尔滨铁路局集团有限公司 哈尔滨机务段
202 HXD3D 631 631 西安铁路局集团有限公司 西安机务段 第五代“钢人铁马号”
203 HXD3D 632 653 沈阳铁路局集团有限公司 沈阳机务段
204 HXD3D 654 673 沈阳铁路局集团有限公司 沈阳机务段
205 HXD3D 674 681 沈阳铁路局集团有限公司 沈阳机务段
206 HXD3D 682 688 兰州铁路局集团有限公司 兰州西机务段
207 HXD3D 1886 1886 哈尔滨铁路局集团有限公司 哈尔滨机务段 第五代“朱德号”
208 HXD3D 1893 1893 北京铁路局集团有限公司 丰台机务段 第六代“毛泽东号”
209 HXD3D 1921 1921 沈阳铁路局集团有限公司 沈阳机务段 共产党员号
210 HXD3D 7001 7002 广西沿海铁路股份有限公司 南宁南机务运用段
211 HXD3D 7003 7003 吉林铁道职业技术学院
212 HXD3D 8001 8025 沈阳铁路局集团有限公司 沈阳机务段 大同
213 HXD3D 8026 8028 太原铁路局集团有限公司 太原南机务段 大同
214 东方红2 1 50 资阳
215 东风 1201 1830 大连、成都
216 东风 2001 2094 戚墅堰
217 东风11 1 459 戚墅堰
218 东风12 8001 8001 吉林铁道职业技术学院
219 东风2 3201 3348 戚墅堰
220 东风21 1 5 中国铁路昆明局集团有限公司 昆明机务段
221 东风21 6 6 中国铁路昆明局集团有限公司 昆明机务段 状元号
222 东风21 7 7 中国铁路昆明局集团有限公司 昆明机务段 亲年号
223 东风21 8 8 中国铁路昆明局集团有限公司 昆明机务段 建水古城
224 东风21 9 100 中国铁路昆明局集团有限公司 昆明机务段
225 东风21 101 101 中国铁路昆明局集团有限公司 昆明机务段 异龙号
226 东风21 102 102 中国铁路昆明局集团有限公司 昆明机务段
227 东风21 1001 1002 云南钢铁厂
228 东风2Z 3251 3251 *齐齐哈尔铁路局 加格达奇机务段
229 东风3 3243 3243 中车共享城机车公园
230 东风4 3247 3247 中车成都轨道交通产业园
231 东风4B 1001 1999 大连
232 东风4B 1963 1963 *北京铁路局 丰台机务段
233 东风4B 2101 2685 大连
234 东风4B 2104 2104 *上海铁路局 蚌埠机务段
235 东风4B 2376 2376 *南昌铁路局 鹰潭机务段
236 东风4B 3101 3999 资阳
237 东风4B 3214 3214 *浙江金温铁道开发有限公司 温州机务段
238 东风4B 3249 3249 *西安铁路局 西安机务段
239 东风4B 3390 3390 *成都铁路局 重庆机务段
240 东风4B 3593 3593 *广州铁路(集团)公司 株洲机务段
241 东风4B 6001 6587 大同
242 东风4B 6530 6530 *南宁铁路局 南宁机务段
243 东风4B 7001 7363 大连
244 东风4B 7364 7365 四方
245 东风4B 7366 7796 大连
246 东风4B 7701 7732 戚墅堰改
247 东风4B 9001 9702 资阳
248 东风4B 9167 9167 *南昌铁路局 向塘机务段
249 东风4B 9531 9531 *新长铁路公司
250 东风4C 1 10 大同
251 东风4C 11 11 北京铁路局 丰台段 青年文明号
252 东风4C 12 40 大同
253 东风4C 2001 2006 四方
254 东风4C 4001 4465 大连
255 东风4C 4466 4466 四方机车车辆厂 四方
256 东风4C 5001 5273 资阳
257 东风4C 5274 5275 三茂铁路公司 三水机务段 东风4CK
258 东风4C 5276 5335 资阳
259 东风4D 7001 7021 中国铁路南宁局集团有限公司
260 东风5 1 1 中国铁路北京局集团有限公司 北京车辆段
261 东风5 1974 1975 中国铁路兰州局集团有限公司 兰州西机务段 唐山
262 东风5 1976 2082 唐山
263 东风5 2083 2083 中国石油兰州石化公司 唐山
264 东风5 3279 3279 云南铁路博物馆
265 东风6 1 2 *沈阳铁路局 大连机务段
266 东风6 3 3 沈阳铁路陈列馆
267 东风6 4 4 *沈阳铁路局 大连机务段
268 东风7 174 174 太原机务段北场
269 东风7B 3006 3006 中国铁道博物馆
270 东风7B 3015 3015 王坪村铁路公园
271 东风7B 6001 6072 *北京铁路局 邯郸机务段;郑州铁路局 新乡机务段 调车
272 东风7D 1 1 中国铁道博物馆
273 东风7D 3001 3001 中国铁道博物馆
274 东风7E 1 1 郑州铁路局 新乡机务段
275 东风7E 2 2 郑州铁路局 郑州机务段
276 东风7G 9001 9004 呼和浩特铁路局 集宁机务段 赛汗塔拉分段
277 东风8 1 1 中国铁道博物馆
278 东风9 1 2 中国铁路广州局集团有限公司广州机务段
279 韶山1 8 8 中国铁道博物馆
280 韶山1 156 156 郑州世纪欢乐园
281 韶山1 160 160 北京铁路电气化学校
282 韶山1 227 227 兰州铁路局 兰州西机务段
283 韶山1 254 254 北京铁路局 丰台机务段 储备厂
284 韶山1 307 307 太原铁路局 榆次机务折返段
285 韶山1 309 309 太原铁路局 太原机务段北场
286 韶山1 321 321 武汉铁路职业技术学院
287 韶山1 681 681 中国铁道博物馆
288 韶山1 695 695 沈阳铁路陈列馆
289 韶山1 762 762 广州铁路(集团)公司 娄底运用车间储备厂
290 韶山1 818 818 西南交通大学 机车博物园
291 韶山1 821 821 韶关机务实训基地
292 韶山1 826 826 韶关机务实训基地
293 韶山3 454 454 成都铁路局 贵阳机务段 先锋号
294 韶山3 524 524 武汉铁路局 江岸机务段 青年号
295 韶山3 4160 4160 广西沿海铁路公司 南宁南机务运用段 共青团号
296 韶山3 4178 4178 广西沿海铁路公司 南宁南机务运用段 共青团号
297 韶山3 4235 4235 成都铁路局 重庆机务段 青年文明号
298 韶山3 4258 4258 成都铁路局 重庆机务段 党员先锋号
299 韶山3 5080 5080 广铁机车博物馆
300 韶山3 6005 6005 湖南交通工程学院
301 韶山3 8050 8050 武汉四美塘铁路遗址公园
302 韶山3B 16 16 西安铁路局 安康机务段 青年文明号
303 韶山3B 5001 5001 成都铁路局 贵阳机务段 *先锋力神
304 韶山3B 5035 5035 兰州铁路局 迎水桥机务段 雷锋号 (曾)
305 韶山3B 5038 5038 兰州铁路局 迎水桥机务段 青年文明号
306 韶山3B 5151 5151 成都铁路局 西昌机务段 扶贫先锋号
307 韶山3B 5162 5162 昆明铁路局 昆明机务段 五四青年号
308 韶山3B 5235 5235 成都铁路局 西昌机务段 *共青团号
309 韶山3C 1 1 贵阳机务段
310 韶山4 6 6 中国铁道博物馆
311 韶山4 10 10 成都铁路局 西昌机务段
312 韶山4 50 50 郑州铁路局 新乡机务段 先锋号
313 韶山4 63 63 太原铁路局 太原机务段
314 韶山4 204 204 郑州铁路局 新乡机务段 先锋号
315 韶山4 448 448 沈阳铁路局 苏家屯机务段 先锋号
316 韶山4 574 574 中铁三局集团 先锋号
317 韶山4 743 743 哈尔滨铁路局 哈尔滨机务段 青年文明号
318 韶山4 855 855 西安铁路局 新丰镇机务段
319 韶山4 911 911 中铁三局集团 青年文明号
320 韶山4 2006 2006 吉林铁道职业技术学院
321 韶山4B 19 19 神朔铁路公司 神木北机务段 青年号
322 韶山4B 89 89 神朔铁路公司 神木北机务段 青年文明号
323 韶山4B 90 90 神朔铁路公司 神木北机务段 青年文明号
324 韶山4B 257 257 包神铁路公司 东胜机务段 党员先锋号
325 韶山4G 159 1177 株洲
326 韶山4G 168 168 中国铁道博物馆
327 韶山4G 171 171 哈尔滨铁路局 牡丹江机务段
328 韶山4G 179 179 太原铁路局 湖东机务段
329 韶山4G 466 466 石家庄铁道大学
330 韶山4G 1089 1089 *呼和浩特铁路局 包头西机务段
331 韶山4G 1886 1886 哈尔滨铁路局 哈尔滨机务段 *朱德号 株洲
332 韶山4G 3001 3002 资阳
333 韶山4G 6001 6001 中国铁道博物馆
334 韶山4G 6001 6001 中国铁道博物馆 大同
335 韶山4G 7001 7110 大连
336 韶山4G 7121 7243 大连
337 韶山5 1 1 中国铁道博物馆
338 韶山5 2 2 郑州世纪欢乐园
339 韶山6 1 1 郑州铁路司机学校
340 韶山6 2 2 中国铁道博物馆
341 韶山6B 1011 1011 西安铁路局 西安机务段 *青年文明号
342 韶山6B 1026 1026 韶关机务实训基地
343 韶山6B 1088 1088 武汉铁路局 襄阳机务段 *民兵号
344 韶山6B 1111 1111 武汉铁路局 襄阳机务段 *先锋号
345 韶山6B 6001 6001 韶关机务实训基地
346 韶山6B 6002 6002 广州铁路博物馆
347 韶山7 1 79 南宁铁路局集团有限公司 柳州机务段
348 韶山7 76 76 南宁铁路局集团有限公司 南宁机务段 *五四红旗号
349 韶山7 80 84 南宁铁路局集团有限公司 柳州机务段
350 韶山7 85 111 南宁铁路局集团有限公司 柳州机务段
351 韶山7 8112 8113 山西孝柳铁路有限责任公司
352 韶山7B 1 1 *南宁铁路局集团有限公司 南宁机务段
353 韶山7B 2 2 中国铁路南宁局集团有限公司 柳州机务段
354 韶山7D 1 58 西安铁路局集团有限公司 西安机务段
355 韶山7D 631 631 西安铁路局集团有限公司 西安机务段 *钢人铁马号
356 韶山7E 1 140 大同
357 韶山7E 6001 6002 昆明铁路局 大同
358 韶山7E 7001 7004 大连
359 韶山8 1 1 中国铁路广州局集团有限公司 广州机务段
360 韶山8 2 2 中国铁路广州局集团有限公司 广州机务段
361 韶山8 3 4 中国铁路上海局集团有限公司 上海机务段
362 韶山8 5 5 中国铁路郑州局集团有限公司 郑州机务段
363 韶山8 9 9 中国铁路郑州局集团有限公司 郑州机务段
364 韶山8 11 11 中国铁路郑州局集团有限公司 郑州机务段
365 韶山8 12 12 中国铁路郑州局集团有限公司 郑州机务段
366 韶山8 15 16 中国铁路郑州局集团有限公司 郑州机务段
367 韶山8 17 17 中国铁路上海局集团有限公司 上海机务段
368 韶山8 20 20 中国铁路郑州局集团有限公司 郑州机务段
369 韶山8 24 25 中国铁路郑州局集团有限公司 郑州机务段
370 韶山8 27 27 中国铁路郑州局集团有限公司 郑州机务段
371 韶山8 29 32 中国铁路郑州局集团有限公司 郑州机务段
372 韶山8 33 35 中国铁路上海局集团有限公司 上海机务段
373 韶山8 36 36 中国铁路郑州局集团有限公司 郑州机务段
374 韶山8 38 38 中国铁路上海局集团有限公司 上海机务段
375 韶山8 39 39 中国铁路上海局集团有限公司 上海机务段 国祥号
376 韶山8 40 40 中国铁路上海局集团有限公司 上海机务段
377 韶山8 41 41 中国铁路北京局集团有限公司 北京机务段
378 韶山8 43 43 中国铁路郑州局集团有限公司 郑州机务段
379 韶山8 44 44 中国铁路北京局集团有限公司 邯郸机务段
380 韶山8 45 45 中国铁路郑州局集团有限公司 郑州机务段
381 韶山8 48 48 中国铁路北京局集团有限公司 邯郸机务段
382 韶山8 49 49 中国铁路南昌局集团有限公司 南昌机务段
383 韶山8 50 50 中国铁路南昌局集团有限公司 南昌机务段
384 韶山8 51 51 中国铁路北京局集团有限公司 邯郸机务段
385 韶山8 52 52 中国铁路上海局集团有限公司 上海机务段
386 韶山8 55 55 中国铁路南昌局集团有限公司 南昌机务段
387 韶山8 56 57 中国铁路北京局集团有限公司 邯郸机务段
388 韶山8 64 64 中国铁路广州局集团有限公司 广州机务段
389 韶山8 72 72 中国铁路北京局集团有限公司 邯郸机务段
390 韶山8 73 73 中国铁路北京局集团有限公司 北京机务段
391 韶山8 74 74 中国铁路北京局集团有限公司 邯郸机务段
392 韶山8 81 81 中国铁路北京局集团有限公司 北京机务段
393 韶山8 83 84 中国铁路郑州局集团有限公司 郑州机务段
394 韶山8 85 85 中国铁路北京局集团有限公司 北京机务段
395 韶山8 88 103 中国铁路郑州局集团有限公司 郑州机务段
396 韶山8 104 104 中国铁路北京局集团有限公司 邯郸机务段
397 韶山8 109 111 中国铁路南昌局集团有限公司 南昌机务段
398 韶山8 114 116 中国铁路南昌局集团有限公司 南昌机务段
399 韶山8 118 119 中国铁路北京局集团有限公司 北京机务段
400 韶山8 121 126 中国铁路北京局集团有限公司 北京机务段
401 韶山8 127 128 中国铁路郑州局集团有限公司 郑州机务段
402 韶山8 130 130 中国铁路南昌局集团有限公司 南昌机务段
403 韶山8 131 131 中国铁路广州局集团有限公司 长沙机务段
404 韶山8 132 132 中国铁路广州局集团有限公司 长沙机务段
405 韶山8 133 133 中国铁路广州局集团有限公司 长沙机务段
406 韶山8 134 134 中国铁路广州局集团有限公司 长沙机务段
407 韶山8 136 136 中国铁路广州局集团有限公司 长沙机务段
408 韶山8 141 141 中国铁路广州局集团有限公司 广州机务段
409 韶山8 144 144 中国铁路广州局集团有限公司 长沙机务段
410 韶山8 148 148 中国铁路广州局集团有限公司 广州机务段
411 韶山8 156 156 中国铁路广州局集团有限公司 广州机务段
412 韶山8 163 163 中国铁路广州局集团有限公司 广州机务段
413 韶山8 166 166 中国铁路广州局集团有限公司 广州机务段 新世纪金龙号
414 韶山8 171 171 中国铁路上海局集团有限公司 上海机务段
415 韶山8 172 172 中国铁路郑州局集团有限公司 郑州机务段
416 韶山8 173 173 中国铁路广州局集团有限公司 广州机务段
417 韶山8 181 181 中国铁路广州局集团有限公司 广州机务段
418 韶山8 186 186 中国铁路广州局集团有限公司 广州机务段
419 韶山8 191 191 中国铁路广州局集团有限公司 广州机务段
420 韶山8 192 192 中国铁路广州局集团有限公司 广州机务段
421 韶山8 197 197 中国铁路郑州局集团有限公司 郑州机务段
422 韶山8 200 204 中国铁路上海局集团有限公司 上海机务段
423 韶山8 205 205 中国铁路广州局集团有限公司 长沙机务段
424 韶山8 214 214 中国铁路郑州局集团有限公司 郑州机务段
425 韶山9 1 3 沈阳铁路局 沈阳机务段;上海铁路局 上海机务段
426 韶山9 5 29 沈阳铁路局 沈阳机务段;上海铁路局 上海机务段
427 韶山9 30 30 沈阳铁路局 通辽机务段
428 韶山9 31 37 沈阳铁路局 沈阳机务段;上海铁路局 上海机务段
429 韶山9 38 38 沈阳铁路局 通辽机务段
430 韶山9 39 43 沈阳铁路局 沈阳机务段;上海铁路局 上海机务段

142
assets/loco_type_info.csv Normal file
View File

@@ -0,0 +1,142 @@
001,解放
003,前进
005,建设
006,KD7
055,蓝箭控车
081,东风21
101,东风
102,东风2
103,东风3
104,东风4
105,东风4客
106,东风4C
107,东风5
108,东风5宽
109,东风6
110,东风7
111,东风8
112,东风9
113,东风10
114,东方红1
115,东方红2
116,东方红3
117,东方红5
118,北京
119,北京宽
120,ND2
121,ND3
122,ND4
123,ND5
124,NY5
125,NY6
126,NY7
127,轻油
128,东方红21
129,东风7B
130,东风5S
131,东风7C
132,东风7S
133,工矿1
134,工矿1F
135,东风4E
136,东风7D
137,工矿1A
138,东风11
139,天安
140,东风10F
141,东风4D
142,东风8B
143,东风12
144,东风7E
145,NYJ1
146,NZJ1
147,NZJ2
148,东风4DJ
149,新曙光
150,神州
151,NJ2
152,东风7G
153,NDJ3
157,FXN3D
158,东风11G
160,HXN3
161,HXN5
162,HXN3B
163,HXN5B
167,FXN3B
169,FXN3C
170,FXN5C
171,FXN3-J
201,8G
202,8K
203,6G
204,6K
205,韶山1
206,韶山3
207,韶山4
208,韶山5
209,韶山6
210,韶山3B
211,韶山7
212,韶山8
213,韶山7B
214,韶山7C
215,韶山6B
216,韶山9
217,韶山7D
218,DJ熊猫
219,DJ1
220,DJ2
221,DJF
222,蓝箭动车
223,先锋号
224,韶山7E
225,韶山4G
226,韶山3C
228,天梭
229,DJ4和谐
230,KTT
231,HXD1
232,HXD2
233,HXD3
234,HXD1B
235,HXD2B
236,HXD3B
237,HXD1C
238,HXD2C
239,HXD3C
240,HXD1D
241,HXD2D
242,HXD3D
243,FXD1B
244,FXD2B
245,FXD1
246,FXD3
247,FXD1-J
248,FXD3-J
249,KZ25TA
251,KZ25TB
252,HXD1D-J
254,FXD1H
300,雪域神州
301,CRH1
302,CRH2
303,CRH3
305,CRH5
306,CRH380A
307,CRH380B
308,CRH380C
309,CRH380D
310,CRH6A
311,CR400AF
312,CR400BF
313,CR300AF
314,CR300BF
315,CRH2E
316,CRH6F
330,CJ1
331,CJ2
332,CJ3
333,CJ4
334,CJ5
335,CJ6
1 001 解放
2 003 前进
3 005 建设
4 006 KD7
5 055 蓝箭控车
6 081 东风21
7 101 东风
8 102 东风2
9 103 东风3
10 104 东风4
11 105 东风4客
12 106 东风4C
13 107 东风5
14 108 东风5宽
15 109 东风6
16 110 东风7
17 111 东风8
18 112 东风9
19 113 东风10
20 114 东方红1
21 115 东方红2
22 116 东方红3
23 117 东方红5
24 118 北京
25 119 北京宽
26 120 ND2
27 121 ND3
28 122 ND4
29 123 ND5
30 124 NY5
31 125 NY6
32 126 NY7
33 127 轻油
34 128 东方红21
35 129 东风7B
36 130 东风5S
37 131 东风7C
38 132 东风7S
39 133 工矿1
40 134 工矿1F
41 135 东风4E
42 136 东风7D
43 137 工矿1A
44 138 东风11
45 139 天安
46 140 东风10F
47 141 东风4D
48 142 东风8B
49 143 东风12
50 144 东风7E
51 145 NYJ1
52 146 NZJ1
53 147 NZJ2
54 148 东风4DJ
55 149 新曙光
56 150 神州
57 151 NJ2
58 152 东风7G
59 153 NDJ3
60 157 FXN3D
61 158 东风11G
62 160 HXN3
63 161 HXN5
64 162 HXN3B
65 163 HXN5B
66 167 FXN3B
67 169 FXN3C
68 170 FXN5C
69 171 FXN3-J
70 201 8G
71 202 8K
72 203 6G
73 204 6K
74 205 韶山1
75 206 韶山3
76 207 韶山4
77 208 韶山5
78 209 韶山6
79 210 韶山3B
80 211 韶山7
81 212 韶山8
82 213 韶山7B
83 214 韶山7C
84 215 韶山6B
85 216 韶山9
86 217 韶山7D
87 218 DJ熊猫
88 219 DJ1
89 220 DJ2
90 221 DJF
91 222 蓝箭动车
92 223 先锋号
93 224 韶山7E
94 225 韶山4G
95 226 韶山3C
96 228 天梭
97 229 DJ4和谐
98 230 KTT
99 231 HXD1
100 232 HXD2
101 233 HXD3
102 234 HXD1B
103 235 HXD2B
104 236 HXD3B
105 237 HXD1C
106 238 HXD2C
107 239 HXD3C
108 240 HXD1D
109 241 HXD2D
110 242 HXD3D
111 243 FXD1B
112 244 FXD2B
113 245 FXD1
114 246 FXD3
115 247 FXD1-J
116 248 FXD3-J
117 249 KZ25TA
118 251 KZ25TB
119 252 HXD1D-J
120 254 FXD1H
121 300 雪域神州
122 301 CRH1
123 302 CRH2
124 303 CRH3
125 305 CRH5
126 306 CRH380A
127 307 CRH380B
128 308 CRH380C
129 309 CRH380D
130 310 CRH6A
131 311 CR400AF
132 312 CR400BF
133 313 CR300AF
134 314 CR300BF
135 315 CRH2E
136 316 CRH6F
137 330 CJ1
138 331 CJ2
139 332 CJ3
140 333 CJ4
141 334 CJ5
142 335 CJ6

View File

@@ -0,0 +1,82 @@
"^[Gg](4000|[1-3]\d{3}|[1-9]\d{0,2})$","直通图定高速动车组"
"^[Gg](400[1-9]|40[1-9]\d|4[1-8]\d{2}|49[0-8]\d|499[0-8])$","直通临客高速动车组"
"^[Gg](9000|[6-8]\d{3}|500[1-9]|50[1-9]\d|5[1-9]\d{2})$","管内图定高速动车组"
"^[Gg](900[1-9]|90[1-9]\d|9[1-8]\d{2}|99[0-8]\d|999[0-8])$","管内临客高速动车组"
"^[Cc]([1-8]\d{3}|9000)$","图定城际动车组"
"^[Cc](900[1-9]|90[1-9]\d|9[1-8]\d{2}|99[0-8]\d|999[0-8])$","临客城际动车组"
"^[Cc][1-9]\d{2}$","动力集中城际动车组"
"^[IDid](4000|[1-3]\d{3}|[1-9]\d{0,2})$","直通图定动车组"
"^[IDid](400[1-9]|40[1-9]\d|4[1-8]\d{2}|49[0-8]\d|499[0-8])$","直通临客动车组"
"^[IDid](9000|[6-8]\d{3}|500[1-9]|50[1-9]\d|5[1-9]\d{2})$","管内图定动车组"
"^[IDid](900[1-9]|90[1-9]\d|9[1-8]\d{2}|99[0-8]\d|999[0-8])$","管内临客动车组"
"^[IDid](8([0-8]\d|9[0-8])|7(0[1-9]|[1-9]\d))$","动力集中动车组"
"^[IDid](300|[12]\d{2}|[1-9]\d?)$","跨局动力集中动车组"
"^[PZpz](4000|[1-3]\d{3}|[1-9]\d{0,2})$","直通图定直达特快旅客列车"
"^[PZpz](400[1-9]|40[1-9]\d|4[1-8]\d{2}|49[0-8]\d|499[0-8])$","直通临客直达特快旅客列车"
"^[PZpz](9000|[6-8]\d{3}|500[1-9]|50[1-9]\d|5[1-9]\d{2})$","管内图定直达特快旅客列车"
"^[PZpz](900[1-9]|90[1-9]\d|9[1-8]\d{2}|99[0-8]\d|999[0-8])$","管内临客直达特快旅客列车"
"^[QTqt](3000|[12]\d{3}|[1-9]\d{0,2})$","直通图定特快旅客列车"
"^[QTqt](300[1-9]|30[1-9]\d|3[1-8]\d{2}|39[0-8]\d|399[0-8])$","直通临客特快旅客列车"
"^[QTqt](4(00[1-9]|0[1-9]\d|[1-8]\d{2}|9[0-8]\d|99[0-8]))$","管内临客特快旅客列车"
"^[QTqt]([5-8]\d{3}|9([0-8]\d{2}|9[0-8]\d|99[0-8])|500[1-9]|50[1-9]\d|5[1-9]\d{2})$","管内图定特快旅客列车"
"^[WKwk](4000|[1-3]\d{3}|[1-9]\d{0,2})$","直通图定快速旅客列车"
"^[WKwk](400[1-9]|40[1-9]\d|4[1-8]\d{2}|49[0-8]\d|499[0-8])$","直通临客快速旅客列车"
"^[WKwk](6([0-8]\d{2}|9[0-8]\d|99[0-8])|500[1-9]|50[1-9]\d|5[1-9]\d{2})$","管内临客快速旅客列车"
"^[WKwk](8\d{3}|9([0-8]\d{2}|9[0-8]\d|99[0-8])|7(00[1-9]|0[1-9]\d|[1-9]\d{2}))$","管内图定快速旅客列车"
"^[Vv1](00[1-9]|0[1-9]\d|[1-9]\d{2})$","跨三局及以上图定普通旅客快车"
"^[Bb2](00[1-9]|0[1-9]\d|[1-9]\d{2})$","跨两局图定普通旅客快车"
"^3(00[1-9]|0[1-9]\d|[1-9]\d{2})$","跨局临时普通旅客快车"
"^[Uu4](00[1-9]|0[1-9]\d|[1-9]\d{2})$","管内图定普通旅客快车"
"^[Xx5](000|1[9][9]|200|3[9][9]|400)$","管内图定普通旅客快车"
"^6(19[0-8]|1[0-8]\d|0[1-9]\d|00[1-9])$","直通普通旅客慢车"
"^(6(20[1-9]|2[1-9]\d|[3-9]\d{2})|7([0-4]\d{2}|5([0-8]\d|9[0-8])))$","管内普通旅客慢车"
"^(8([0-8]\d{2}|9[0-8]\d|99[0-8])|7(60[1-9]|6[1-9]\d|[7-9]\d{2}))$","通勤列车"
"^[Yy](500|[1-4]\d{2}|[1-9]\d?)$","跨局旅游列车"
"^[Yy](50[1-9]|5[1-9]\d|[6-9]\d{2})$","管内旅游列车"
"^[Ss][1-9]\d{0,3}$","市郊旅客列车"
"^[Ll](6([0-8]\d{2}|9[0-8]\d|99[0-8])|[1-5]\d{3}|[1-9]\d{0,2})$","直通临时旅客列车"
"^[Ll]([7-9]\d{3})$","管内临时旅客列车"
"^[Xx](19[0-8]|1[0-8]\d|[1-9]\d?)$","特快货物班列"
"^[Xx](39[0-8]|3[0-8]\d|2[1-9]\d|20[1-9])$","快速货物班列"
"^[Xx]2(40[1-9]|4[1-9]\d|[5-9]\d{2})$","直通货物快运列车"
"^[Xx]([4-9]\d{2}|4[1-9]\d|40[1-9])$","管内货物快运列车"
"^[Xx]8\d{3}$","中欧中亚集装箱班列"
"^[Xx]9([0-4]\d{2}|500)$","中亚集装箱班列"
"^[Xx]9(50[1-9]|5[1-9]\d|[6-9]\d{2})$","水铁联运班列"
"^[Xx][1-4]\d{4}$","加挂零散快运车辆货物列车"
"^1(000[1-9]|00[1-9]\d|0[1-9]\d{2}|[1-9]\d{3})$","技术直达列车"
"^2\d{4}$","直通货物列车"
"^3\d{4}$","区段摘挂列车"
"^4([0-3]\d{3}|4([0-8]\d{2}|9[0-8]\d|99[0-8]))$","摘挂列车"
"^4(500[1-9]|50[1-9]\d|5[1-9]\d{2}|[6-9]\d{3})$","小运转列车"
"^6\d{4}$","自备列车"
"^70\d{3}$","超限货物列车"
"^7([1-6]\d{3}|7([0-8]\d{2}|9[0-8]\d|99[0-8]))$","重载货物列车"
"^78\d{3}$","保温列车"
"^8(0\d{3}|1([0-8]\d{2}|9[0-8]\d|99[0-8]))$","普快货物班列"
"^8(200[1-9]|20[1-9]\d|2[1-9]\d{2}|[34]\d{3})$","煤炭直达列车"
"^85\d{3}$","石油直达列车"
"^86\d{3}$","始发直达列车"
"^87\d{3}$","空车直达列车"
"^(90\d{3}|91([0-8]\d{2}|9[0-8]\d|99[0-8]))$","军用列车"
"^50\d{3}$","客车单机"
"^51\d{3}$","货车单机"
"^52\d{3}$","小运转单机"
"^5(3\d{3}|4([0-8]\d{2}|9[0-8]\d|99[0-8]))$","补机列车"
"^55(300|[0-2]\d{2})$","普通客货试运转列车"
"^55(500|30[1-9]|3[1-9]\d|4\d{2})$","高速动车组试运转列车"
"^55(50[1-9]|5[1-9]\d|[6-9]\d{2})$","普通动车组试运转列车"
"^56\d{3}$","轻油动车与轨道车"
"^57\d{3}$","路用列车"
"^58(10[1-9]|1[1-9]\d|[2-8]\d{2}|9([0-8]\d|9[0-8]))$","救援列车"
"^DJ(400|[1-3]\d{2}|[1-9]\d?)$","动车组检测列车300直通"
"^DJ([4-9]\d{2}|40[1-9]|4[1-9]\d)$","动车组检测列车300管内"
"^DJ1(400|[0-3]\d{2})$","动车组检测列车250直通"
"^DJ1(40[1-9]|4[1-9]\d|[5-9]\d{2})$","动车组检测列车250管内"
"^DJ[56]\d{3}$","直通动车组确认列车"
"^DJ[78]\d{3}$","管内动车组确认列车"
"^[Ff][GDCZTKgdcztk]?\d{1,4}$","因故折返旅客列车"
"^0[GDCZTKgdcztk]\d{1,4}$","回送图定客车底"
"^00(100|[1-9]\d?)$","有火回送动车组车底"
"^00(10[1-9]|1[1-9]\d|2([0-8]\d|9[0-8]))$","无火回送动车组车底"
"^00(30[1-9]|3[1-9]\d|4([0-8]\d|9[0-8]))$","无火回送普速客车底"
1 ^[Gg](4000|[1-3]\d{3}|[1-9]\d{0,2})$ 直通图定高速动车组
2 ^[Gg](400[1-9]|40[1-9]\d|4[1-8]\d{2}|49[0-8]\d|499[0-8])$ 直通临客高速动车组
3 ^[Gg](9000|[6-8]\d{3}|500[1-9]|50[1-9]\d|5[1-9]\d{2})$ 管内图定高速动车组
4 ^[Gg](900[1-9]|90[1-9]\d|9[1-8]\d{2}|99[0-8]\d|999[0-8])$ 管内临客高速动车组
5 ^[Cc]([1-8]\d{3}|9000)$ 图定城际动车组
6 ^[Cc](900[1-9]|90[1-9]\d|9[1-8]\d{2}|99[0-8]\d|999[0-8])$ 临客城际动车组
7 ^[Cc][1-9]\d{2}$ 动力集中城际动车组
8 ^[IDid](4000|[1-3]\d{3}|[1-9]\d{0,2})$ 直通图定动车组
9 ^[IDid](400[1-9]|40[1-9]\d|4[1-8]\d{2}|49[0-8]\d|499[0-8])$ 直通临客动车组
10 ^[IDid](9000|[6-8]\d{3}|500[1-9]|50[1-9]\d|5[1-9]\d{2})$ 管内图定动车组
11 ^[IDid](900[1-9]|90[1-9]\d|9[1-8]\d{2}|99[0-8]\d|999[0-8])$ 管内临客动车组
12 ^[IDid](8([0-8]\d|9[0-8])|7(0[1-9]|[1-9]\d))$ 动力集中动车组
13 ^[IDid](300|[12]\d{2}|[1-9]\d?)$ 跨局动力集中动车组
14 ^[PZpz](4000|[1-3]\d{3}|[1-9]\d{0,2})$ 直通图定直达特快旅客列车
15 ^[PZpz](400[1-9]|40[1-9]\d|4[1-8]\d{2}|49[0-8]\d|499[0-8])$ 直通临客直达特快旅客列车
16 ^[PZpz](9000|[6-8]\d{3}|500[1-9]|50[1-9]\d|5[1-9]\d{2})$ 管内图定直达特快旅客列车
17 ^[PZpz](900[1-9]|90[1-9]\d|9[1-8]\d{2}|99[0-8]\d|999[0-8])$ 管内临客直达特快旅客列车
18 ^[QTqt](3000|[12]\d{3}|[1-9]\d{0,2})$ 直通图定特快旅客列车
19 ^[QTqt](300[1-9]|30[1-9]\d|3[1-8]\d{2}|39[0-8]\d|399[0-8])$ 直通临客特快旅客列车
20 ^[QTqt](4(00[1-9]|0[1-9]\d|[1-8]\d{2}|9[0-8]\d|99[0-8]))$ 管内临客特快旅客列车
21 ^[QTqt]([5-8]\d{3}|9([0-8]\d{2}|9[0-8]\d|99[0-8])|500[1-9]|50[1-9]\d|5[1-9]\d{2})$ 管内图定特快旅客列车
22 ^[WKwk](4000|[1-3]\d{3}|[1-9]\d{0,2})$ 直通图定快速旅客列车
23 ^[WKwk](400[1-9]|40[1-9]\d|4[1-8]\d{2}|49[0-8]\d|499[0-8])$ 直通临客快速旅客列车
24 ^[WKwk](6([0-8]\d{2}|9[0-8]\d|99[0-8])|500[1-9]|50[1-9]\d|5[1-9]\d{2})$ 管内临客快速旅客列车
25 ^[WKwk](8\d{3}|9([0-8]\d{2}|9[0-8]\d|99[0-8])|7(00[1-9]|0[1-9]\d|[1-9]\d{2}))$ 管内图定快速旅客列车
26 ^[Vv1](00[1-9]|0[1-9]\d|[1-9]\d{2})$ 跨三局及以上图定普通旅客快车
27 ^[Bb2](00[1-9]|0[1-9]\d|[1-9]\d{2})$ 跨两局图定普通旅客快车
28 ^3(00[1-9]|0[1-9]\d|[1-9]\d{2})$ 跨局临时普通旅客快车
29 ^[Uu4](00[1-9]|0[1-9]\d|[1-9]\d{2})$ 管内图定普通旅客快车
30 ^[Xx5](000|1[9][9]|200|3[9][9]|400)$ 管内图定普通旅客快车
31 ^6(19[0-8]|1[0-8]\d|0[1-9]\d|00[1-9])$ 直通普通旅客慢车
32 ^(6(20[1-9]|2[1-9]\d|[3-9]\d{2})|7([0-4]\d{2}|5([0-8]\d|9[0-8])))$ 管内普通旅客慢车
33 ^(8([0-8]\d{2}|9[0-8]\d|99[0-8])|7(60[1-9]|6[1-9]\d|[7-9]\d{2}))$ 通勤列车
34 ^[Yy](500|[1-4]\d{2}|[1-9]\d?)$ 跨局旅游列车
35 ^[Yy](50[1-9]|5[1-9]\d|[6-9]\d{2})$ 管内旅游列车
36 ^[Ss][1-9]\d{0,3}$ 市郊旅客列车
37 ^[Ll](6([0-8]\d{2}|9[0-8]\d|99[0-8])|[1-5]\d{3}|[1-9]\d{0,2})$ 直通临时旅客列车
38 ^[Ll]([7-9]\d{3})$ 管内临时旅客列车
39 ^[Xx](19[0-8]|1[0-8]\d|[1-9]\d?)$ 特快货物班列
40 ^[Xx](39[0-8]|3[0-8]\d|2[1-9]\d|20[1-9])$ 快速货物班列
41 ^[Xx]2(40[1-9]|4[1-9]\d|[5-9]\d{2})$ 直通货物快运列车
42 ^[Xx]([4-9]\d{2}|4[1-9]\d|40[1-9])$ 管内货物快运列车
43 ^[Xx]8\d{3}$ 中欧中亚集装箱班列
44 ^[Xx]9([0-4]\d{2}|500)$ 中亚集装箱班列
45 ^[Xx]9(50[1-9]|5[1-9]\d|[6-9]\d{2})$ 水铁联运班列
46 ^[Xx][1-4]\d{4}$ 加挂零散快运车辆货物列车
47 ^1(000[1-9]|00[1-9]\d|0[1-9]\d{2}|[1-9]\d{3})$ 技术直达列车
48 ^2\d{4}$ 直通货物列车
49 ^3\d{4}$ 区段摘挂列车
50 ^4([0-3]\d{3}|4([0-8]\d{2}|9[0-8]\d|99[0-8]))$ 摘挂列车
51 ^4(500[1-9]|50[1-9]\d|5[1-9]\d{2}|[6-9]\d{3})$ 小运转列车
52 ^6\d{4}$ 自备列车
53 ^70\d{3}$ 超限货物列车
54 ^7([1-6]\d{3}|7([0-8]\d{2}|9[0-8]\d|99[0-8]))$ 重载货物列车
55 ^78\d{3}$ 保温列车
56 ^8(0\d{3}|1([0-8]\d{2}|9[0-8]\d|99[0-8]))$ 普快货物班列
57 ^8(200[1-9]|20[1-9]\d|2[1-9]\d{2}|[34]\d{3})$ 煤炭直达列车
58 ^85\d{3}$ 石油直达列车
59 ^86\d{3}$ 始发直达列车
60 ^87\d{3}$ 空车直达列车
61 ^(90\d{3}|91([0-8]\d{2}|9[0-8]\d|99[0-8]))$ 军用列车
62 ^50\d{3}$ 客车单机
63 ^51\d{3}$ 货车单机
64 ^52\d{3}$ 小运转单机
65 ^5(3\d{3}|4([0-8]\d{2}|9[0-8]\d|99[0-8]))$ 补机列车
66 ^55(300|[0-2]\d{2})$ 普通客货试运转列车
67 ^55(500|30[1-9]|3[1-9]\d|4\d{2})$ 高速动车组试运转列车
68 ^55(50[1-9]|5[1-9]\d|[6-9]\d{2})$ 普通动车组试运转列车
69 ^56\d{3}$ 轻油动车与轨道车
70 ^57\d{3}$ 路用列车
71 ^58(10[1-9]|1[1-9]\d|[2-8]\d{2}|9([0-8]\d|9[0-8]))$ 救援列车
72 ^DJ(400|[1-3]\d{2}|[1-9]\d?)$ 动车组检测列车300直通
73 ^DJ([4-9]\d{2}|40[1-9]|4[1-9]\d)$ 动车组检测列车300管内
74 ^DJ1(400|[0-3]\d{2})$ 动车组检测列车250直通
75 ^DJ1(40[1-9]|4[1-9]\d|[5-9]\d{2})$ 动车组检测列车250管内
76 ^DJ[56]\d{3}$ 直通动车组确认列车
77 ^DJ[78]\d{3}$ 管内动车组确认列车
78 ^[Ff][GDCZTKgdcztk]?\d{1,4}$ 因故折返旅客列车
79 ^0[GDCZTKgdcztk]\d{1,4}$ 回送图定客车底
80 ^00(100|[1-9]\d?)$ 有火回送动车组车底
81 ^00(10[1-9]|1[1-9]\d|2([0-8]\d|9[0-8]))$ 无火回送动车组车底
82 ^00(30[1-9]|3[1-9]\d|4([0-8]\d|9[0-8]))$ 无火回送普速客车底

34
ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; 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>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole.flutter;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole.flutter.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole.flutter.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole.flutter.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole.flutter;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

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

View File

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

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

49
ios/Runner/Info.plist Normal file
View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Lbjconsole</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>lbjconsole</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

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

View File

@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

17
lbjconsole.iml Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>

35
lib/main.dart Normal file
View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:lbjconsole/screens/main_screen.dart';
import 'package:lbjconsole/util/train_type_util.dart';
import 'package:lbjconsole/util/loco_info_util.dart';
import 'package:lbjconsole/util/loco_type_util.dart';
import 'package:lbjconsole/services/loco_type_service.dart';
import 'package:lbjconsole/services/database_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Future.wait([
TrainTypeUtil.initialize(),
LocoInfoUtil.initialize(),
LocoTypeService().initialize(),
]);
runApp(const LBJReceiverApp());
}
class LBJReceiverApp extends StatelessWidget {
const LBJReceiverApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'LBJ Console',
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.dark,
home: const MainScreen(),
);
}
}

View File

@@ -0,0 +1,60 @@
import 'package:lbjconsole/models/train_record.dart';
class MergedTrainRecord {
final String groupKey;
final List<TrainRecord> records;
final TrainRecord latestRecord;
MergedTrainRecord({
required this.groupKey,
required this.records,
required this.latestRecord,
});
int get recordCount => records.length;
}
class MergeSettings {
final bool enabled;
final GroupBy groupBy;
final TimeWindow timeWindow;
MergeSettings({
this.enabled = true,
this.groupBy = GroupBy.trainAndLoco,
this.timeWindow = TimeWindow.unlimited,
});
factory MergeSettings.fromMap(Map<String, dynamic> map) {
return MergeSettings(
enabled: (map['mergeRecordsEnabled'] ?? 0) == 1,
groupBy: GroupBy.values.firstWhere(
(e) => e.name == map['groupBy'],
orElse: () => GroupBy.trainAndLoco,
),
timeWindow: TimeWindow.values.firstWhere(
(e) => e.name == map['timeWindow'],
orElse: () => TimeWindow.unlimited,
),
);
}
}
enum GroupBy {
trainOnly,
locoOnly,
trainOrLoco,
trainAndLoco,
}
enum TimeWindow {
oneHour(Duration(hours: 1)),
twoHours(Duration(hours: 2)),
sixHours(Duration(hours: 6)),
twelveHours(Duration(hours: 12)),
oneDay(Duration(days: 1)),
unlimited(null);
final Duration? duration;
const TimeWindow(this.duration);
}

View File

@@ -0,0 +1,301 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:lbjconsole/util/train_type_util.dart';
import 'package:lbjconsole/util/loco_info_util.dart';
class TrainRecord {
final String uniqueId;
final DateTime timestamp;
final DateTime receivedTimestamp;
final String train;
final int direction;
final String speed;
final String position;
final String time;
final String loco;
final String locoType;
final String lbjClass;
final String route;
final String positionInfo;
final double rssi;
TrainRecord({
required this.uniqueId,
required this.timestamp,
required this.receivedTimestamp,
required this.train,
required this.direction,
required this.speed,
required this.position,
required this.time,
required this.loco,
required this.locoType,
required this.lbjClass,
required this.route,
required this.positionInfo,
required this.rssi,
});
factory TrainRecord.fromJson(Map<String, dynamic> json) {
return TrainRecord(
uniqueId: json['uniqueId'] ?? json['unique_id'] ?? '',
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] ?? 0),
receivedTimestamp: DateTime.fromMillisecondsSinceEpoch(
json['receivedTimestamp'] ?? json['received_timestamp'] ?? 0),
train: json['train'] ?? '',
direction: json['direction'] ?? json['dir'] ?? 0,
speed: json['speed'] ?? '',
position: json['position'] ?? json['pos'] ?? '',
time: json['time'] ?? '',
loco: json['loco'] ?? '',
locoType: json['locoType'] ?? json['loco_type'] ?? '',
lbjClass: json['lbjClass'] ?? json['lbj_class'] ?? '',
route: json['route'] ?? '',
positionInfo: json['positionInfo'] ?? json['position_info'] ?? '',
rssi: (json['rssi'] ?? 0.0).toDouble(),
);
}
factory TrainRecord.fromJsonString(String jsonString) {
final json = jsonDecode(jsonString);
return TrainRecord.fromJson(json);
}
Map<String, dynamic> toJson() {
return {
'uniqueId': uniqueId,
'timestamp': timestamp.millisecondsSinceEpoch,
'receivedTimestamp': receivedTimestamp.millisecondsSinceEpoch,
'train': train,
'direction': direction,
'speed': speed,
'position': position,
'time': time,
'loco': loco,
'loco_type': locoType,
'lbj_class': lbjClass,
'route': route,
'position_info': positionInfo,
'rssi': rssi,
};
}
Map<String, dynamic> toDatabaseJson() {
return {
'uniqueId': uniqueId,
'timestamp': timestamp.millisecondsSinceEpoch,
'receivedTimestamp': receivedTimestamp.millisecondsSinceEpoch,
'train': train,
'direction': direction,
'speed': speed,
'position': position,
'time': time,
'loco': loco,
'locoType': locoType,
'lbjClass': lbjClass,
'route': route,
'positionInfo': positionInfo,
'rssi': rssi,
};
}
factory TrainRecord.fromDatabaseJson(Map<String, dynamic> json) {
return TrainRecord(
uniqueId: json['uniqueId']?.toString() ?? '',
timestamp:
DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int? ?? 0),
receivedTimestamp: DateTime.fromMillisecondsSinceEpoch(
json['receivedTimestamp'] as int? ?? 0),
train: json['train']?.toString() ?? '',
direction: json['direction'] as int? ?? 0,
speed: json['speed']?.toString() ?? '',
position: json['position']?.toString() ?? '',
time: json['time']?.toString() ?? '',
loco: json['loco']?.toString() ?? '',
locoType: json['locoType']?.toString() ?? '',
lbjClass: json['lbjClass']?.toString() ?? '',
route: json['route']?.toString() ?? '',
positionInfo: json['positionInfo']?.toString() ?? '',
rssi: (json['rssi'] as num?)?.toDouble() ?? 0.0,
);
}
String get directionText {
switch (direction) {
case 0:
return '上行';
case 1:
return '下行';
default:
return '未知';
}
}
String get locoTypeText {
if (locoType.isEmpty) return '未知';
return locoType;
}
String get trainType {
final lbjClassValue = lbjClass.isEmpty ? "NA" : lbjClass;
return TrainTypeUtil.getTrainType(lbjClassValue, train) ?? '未知';
}
String? get locoInfo {
return LocoInfoUtil.getLocoInfoDisplay(locoType, train);
}
String get fullTrainNumber {
final lbjClassValue = lbjClass.trim();
final trainValue = train.trim();
if (trainValue == "<NUL>") {
return "";
}
if (lbjClassValue.isEmpty || lbjClassValue == "NA") {
return trainValue;
} else {
return "$lbjClassValue$trainValue";
}
}
String get lbjClassText {
if (lbjClass.isEmpty) return '未知';
return lbjClass;
}
double get speedValue {
try {
return double.parse(speed.replaceAll(RegExp(r'[^\d.]'), ''));
} catch (e) {
return 0.0;
}
}
String get speedUnit {
if (speed.contains('km/h')) return 'km/h';
if (speed.contains('m/s')) return 'm/s';
return '';
}
String get formattedTime {
return '${timestamp.hour.toString().padLeft(2, '0')}:${timestamp.minute.toString().padLeft(2, '0')}:${timestamp.second.toString().padLeft(2, '0')}';
}
String get formattedDate {
return '${timestamp.year}-${timestamp.month.toString().padLeft(2, '0')}-${timestamp.day.toString().padLeft(2, '0')}';
}
String get relativeTime {
final now = DateTime.now();
final difference = now.difference(timestamp);
if (difference.inMinutes < 1) {
return '刚刚';
} else if (difference.inHours < 1) {
return '${difference.inMinutes}分钟前';
} else if (difference.inDays < 1) {
return '${difference.inHours}小时前';
} else if (difference.inDays < 7) {
return '${difference.inDays}天前';
} else {
return formattedDate;
}
}
String get rssiDescription {
if (rssi > -50) return '';
if (rssi > -70) return '';
if (rssi > -90) return '';
return '无信号';
}
Color get rssiColor {
if (rssi > -50) return Colors.green;
if (rssi > -70) return Colors.orange;
if (rssi > -90) return Colors.red;
return Colors.grey;
}
Map<String, dynamic> toMap() {
return {
'uniqueId': uniqueId,
'timestamp': timestamp.millisecondsSinceEpoch,
'receivedTimestamp': receivedTimestamp.millisecondsSinceEpoch,
'train': train,
'direction': direction,
'speed': speed,
'position': position,
'time': time,
'loco': loco,
'locoType': locoType,
'lbjClass': lbjClass,
'route': route,
'positionInfo': positionInfo,
'rssi': rssi,
};
}
Map<String, double> getCoordinates() {
final parts = position.split(',');
if (parts.length >= 2) {
try {
final lat = double.parse(parts[0].trim());
final lng = double.parse(parts[1].trim());
return {'lat': lat, 'lng': lng};
} catch (e) {
return {'lat': 0.0, 'lng': 0.0};
}
}
return {'lat': 0.0, 'lng': 0.0};
}
TrainRecord copyWith({
String? uniqueId,
DateTime? timestamp,
DateTime? receivedTimestamp,
String? train,
int? direction,
String? speed,
String? position,
String? time,
String? loco,
String? locoType,
String? lbjClass,
String? route,
String? positionInfo,
double? rssi,
}) {
return TrainRecord(
uniqueId: uniqueId ?? this.uniqueId,
timestamp: timestamp ?? this.timestamp,
receivedTimestamp: receivedTimestamp ?? this.receivedTimestamp,
train: train ?? this.train,
direction: direction ?? this.direction,
speed: speed ?? this.speed,
position: position ?? this.position,
time: time ?? this.time,
loco: loco ?? this.loco,
locoType: locoType ?? this.locoType,
lbjClass: lbjClass ?? this.lbjClass,
route: route ?? this.route,
positionInfo: positionInfo ?? this.positionInfo,
rssi: rssi ?? this.rssi,
);
}
@override
String toString() {
return 'TrainRecord(uniqueId: $uniqueId, train: $train, direction: $direction, speed: $speed, position: $position)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is TrainRecord && other.uniqueId == uniqueId;
}
@override
int get hashCode => uniqueId.hashCode;
}

View File

@@ -0,0 +1,566 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:lbjconsole/models/merged_record.dart';
import 'package:lbjconsole/services/database_service.dart';
import 'package:lbjconsole/models/train_record.dart';
import 'package:lbjconsole/services/merge_service.dart';
class HistoryScreen extends StatefulWidget {
final Function(bool isEditing) onEditModeChanged;
final Function() onSelectionChanged;
const HistoryScreen({
super.key,
required this.onEditModeChanged,
required this.onSelectionChanged,
});
@override
HistoryScreenState createState() => HistoryScreenState();
}
class HistoryScreenState extends State<HistoryScreen> {
final List<Object> _displayItems = [];
bool _isLoading = true;
bool _isEditMode = false;
final Set<String> _selectedRecords = {};
final Map<String, bool> _expandedStates = {};
final ScrollController _scrollController = ScrollController();
bool _isAtTop = true;
MergeSettings _mergeSettings = MergeSettings();
int getSelectedCount() => _selectedRecords.length;
Set<String> getSelectedRecordIds() => _selectedRecords;
List<Object> getDisplayItems() => _displayItems;
void clearSelection() => setState(() => _selectedRecords.clear());
void setEditMode(bool isEditing) {
setState(() {
_isEditMode = isEditing;
widget.onEditModeChanged(isEditing);
if (!isEditing) {
_selectedRecords.clear();
}
});
}
@override
void initState() {
super.initState();
loadRecords();
_scrollController.addListener(() {
if (_scrollController.position.atEdge) {
if (_scrollController.position.pixels == 0) {
if (!_isAtTop) setState(() => _isAtTop = true);
}
} else {
if (_isAtTop) setState(() => _isAtTop = false);
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Future<void> loadRecords({bool scrollToTop = true}) async {
if (mounted) setState(() => _isLoading = true);
try {
final allRecords = await DatabaseService.instance.getAllRecords();
final settingsMap = await DatabaseService.instance.getAllSettings() ?? {};
_mergeSettings = MergeSettings.fromMap(settingsMap);
final items = MergeService.getMixedList(allRecords, _mergeSettings);
if (mounted) {
setState(() {
_displayItems.clear();
_displayItems.addAll(items);
_isLoading = false;
});
if (scrollToTop && (_isAtTop) && _scrollController.hasClients) {
_scrollController.animateTo(0.0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut);
}
}
} catch (e) {
if (mounted) setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (_displayItems.isEmpty) {
return const Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.history, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('暂无记录', style: TextStyle(color: Colors.white, fontSize: 18))
]));
}
return ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(16.0),
itemCount: _displayItems.length,
itemBuilder: (context, index) {
final item = _displayItems[index];
if (item is MergedTrainRecord) {
return _buildMergedRecordCard(item);
} else if (item is TrainRecord) {
return _buildRecordCard(item);
}
return const SizedBox.shrink();
});
}
Widget _buildMergedRecordCard(MergedTrainRecord mergedRecord) {
final bool isSelected =
mergedRecord.records.any((r) => _selectedRecords.contains(r.uniqueId));
final isExpanded = _expandedStates[mergedRecord.groupKey] ?? false;
return Card(
color: isSelected && _isEditMode
? const Color(0xFF2E2E2E)
: const Color(0xFF1E1E1E),
elevation: 1,
margin: const EdgeInsets.only(bottom: 8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
side: BorderSide(
color: isSelected && _isEditMode
? Colors.blue
: Colors.transparent,
width: 2.0)),
child: InkWell(
borderRadius: BorderRadius.circular(8.0),
onTap: () {
if (_isEditMode) {
setState(() {
final allIdsInGroup =
mergedRecord.records.map((r) => r.uniqueId).toSet();
if (isSelected) {
_selectedRecords.removeAll(allIdsInGroup);
} else {
_selectedRecords.addAll(allIdsInGroup);
}
widget.onSelectionChanged();
});
} else {
setState(
() => _expandedStates[mergedRecord.groupKey] = !isExpanded);
}
},
onLongPress: () {
if (!_isEditMode) setEditMode(true);
setState(() {
final allIdsInGroup =
mergedRecord.records.map((r) => r.uniqueId).toSet();
_selectedRecords.addAll(allIdsInGroup);
widget.onSelectionChanged();
});
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildRecordHeader(mergedRecord.latestRecord,
isMerged: true),
_buildPositionAndSpeed(mergedRecord.latestRecord),
_buildLocoInfo(mergedRecord.latestRecord),
if (isExpanded) _buildMergedExpandedContent(mergedRecord)
]))));
}
Widget _buildMergedExpandedContent(MergedTrainRecord mergedRecord) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildExpandedMapForAll(mergedRecord.records),
const Divider(color: Colors.white24, height: 24),
...mergedRecord.records.map((record) => _buildSubRecordItem(
record, mergedRecord.latestRecord, _mergeSettings.groupBy)),
],
);
}
Widget _buildSubRecordItem(
TrainRecord record, TrainRecord latest, GroupBy groupBy) {
String differingInfo = _getDifferingInfo(record, latest, groupBy);
String locationInfo = _getLocationInfo(record);
return Padding(
padding: const EdgeInsets.only(bottom: 8.0, top: 4.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
record.receivedTimestamp.toString().split('.')[0],
style: const TextStyle(color: Colors.grey, fontSize: 12),
),
if (differingInfo.isNotEmpty)
Text(
differingInfo,
style:
const TextStyle(color: Color(0xFF81D4FA), fontSize: 12),
),
],
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
locationInfo,
style: const TextStyle(color: Colors.white70, fontSize: 14),
overflow: TextOverflow.ellipsis,
),
),
Text(
record.speed.isNotEmpty ? "${record.speed} km/h" : "",
style: const TextStyle(color: Colors.white70, fontSize: 14),
),
],
),
],
),
);
}
String _getDifferingInfo(
TrainRecord record, TrainRecord latest, GroupBy groupBy) {
final train = record.train.trim();
final loco = record.loco.trim();
final latestTrain = latest.train.trim();
final latestLoco = latest.loco.trim();
switch (groupBy) {
case GroupBy.trainOnly:
return loco != latestLoco && loco.isNotEmpty ? "机车: $loco" : "";
case GroupBy.locoOnly:
return train != latestTrain && train.isNotEmpty ? "车次: $train" : "";
case GroupBy.trainOrLoco:
if (train.isNotEmpty && train != latestTrain) return "车次: $train";
if (loco.isNotEmpty && loco != latestLoco) return "机车: $loco";
return "";
case GroupBy.trainAndLoco:
return "";
}
}
String _getLocationInfo(TrainRecord record) {
List<String> parts = [];
if (record.route.isNotEmpty && record.route != "<NUL>")
parts.add(record.route);
if (record.direction != 0) parts.add(record.direction == 1 ? "" : "");
if (record.position.isNotEmpty && record.position != "<NUL>")
parts.add("${record.position}K");
return parts.join(' ');
}
Widget _buildExpandedMapForAll(List<TrainRecord> records) {
final positions = records
.map((record) => _parsePosition(record.positionInfo))
.whereType<LatLng>()
.toList();
if (positions.isEmpty) return const SizedBox.shrink();
final bounds = LatLngBounds.fromPoints(positions);
return Column(children: [
const SizedBox(height: 8),
Container(
height: 220,
margin: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), color: Colors.grey[900]),
child: FlutterMap(
options: MapOptions(
initialCenter: bounds.center,
initialZoom: 10,
minZoom: 5,
maxZoom: 18,
cameraConstraint: CameraConstraint.contain(bounds: bounds)),
children: [
TileLayer(
urlTemplate:
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'org.noxylva.lbjconsole'),
MarkerLayer(
markers: positions
.map((pos) => Marker(
point: pos,
width: 40,
height: 40,
child: Container(
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.8),
shape: BoxShape.circle,
border: Border.all(
color: Colors.white, width: 2)),
child: const Icon(Icons.train,
color: Colors.white, size: 20))))
.toList())
]))
]);
}
Widget _buildRecordCard(TrainRecord record, {bool isSubCard = false}) {
final isSelected = _selectedRecords.contains(record.uniqueId);
final isExpanded =
!isSubCard && (_expandedStates[record.uniqueId] ?? false);
return Card(
color: isSelected && _isEditMode
? const Color(0xFF2E2E2E)
: const Color(0xFF1E1E1E),
elevation: isSubCard ? 0 : 1,
margin: EdgeInsets.only(bottom: isSubCard ? 4.0 : 8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
side: BorderSide(
color: isSelected && _isEditMode
? Colors.blue
: Colors.transparent,
width: 2.0)),
child: InkWell(
borderRadius: BorderRadius.circular(8.0),
onTap: () {
if (_isEditMode) {
setState(() {
if (isSelected) {
_selectedRecords.remove(record.uniqueId);
} else {
_selectedRecords.add(record.uniqueId);
}
widget.onSelectionChanged();
});
} else if (!isSubCard) {
setState(() => _expandedStates[record.uniqueId] = !isExpanded);
}
},
onLongPress: () {
if (!_isEditMode) setEditMode(true);
setState(() {
_selectedRecords.add(record.uniqueId);
widget.onSelectionChanged();
});
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildRecordHeader(record),
_buildPositionAndSpeed(record),
_buildLocoInfo(record),
if (isExpanded) _buildExpandedContent(record)
]))));
}
Widget _buildRecordHeader(TrainRecord record, {bool isMerged = false}) {
final trainType = record.trainType;
final trainDisplay =
record.fullTrainNumber.isEmpty ? "未知列车" : record.fullTrainNumber;
String formattedLocoInfo = "";
if (record.locoType.isNotEmpty && record.loco.isNotEmpty) {
final shortLoco = record.loco.length > 5
? record.loco.substring(record.loco.length - 5)
: record.loco;
formattedLocoInfo = "${record.locoType}-$shortLoco";
} else if (record.locoType.isNotEmpty) {
formattedLocoInfo = record.locoType;
} else if (record.loco.isNotEmpty) {
formattedLocoInfo = record.loco;
}
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Flexible(
child: Text(
(record.time == "<NUL>" || record.time.isEmpty)
? record.receivedTimestamp.toString().split(".")[0]
: record.time.split("\n")[0],
style: const TextStyle(fontSize: 12, color: Colors.grey),
overflow: TextOverflow.ellipsis)),
if (trainType.isNotEmpty)
Flexible(
child: Text(trainType,
style: const TextStyle(fontSize: 12, color: Colors.grey),
overflow: TextOverflow.ellipsis))
]),
const SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(trainDisplay,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
overflow: TextOverflow.ellipsis)),
const SizedBox(width: 6),
if (record.direction == 1 || record.direction == 3)
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(2)),
child: Center(
child: Text(record.direction == 1 ? "" : "",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.black))))
])),
if (formattedLocoInfo.isNotEmpty && formattedLocoInfo != "<NUL>")
Text(formattedLocoInfo,
style: const TextStyle(fontSize: 14, color: Colors.white70))
]),
const SizedBox(height: 2)
]);
}
Widget _buildLocoInfo(TrainRecord record) {
final locoInfo = record.locoInfo;
if (locoInfo == null || locoInfo.isEmpty) return const SizedBox.shrink();
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const SizedBox(height: 4),
Text(locoInfo,
style: const TextStyle(fontSize: 14, color: Colors.white),
maxLines: 1,
overflow: TextOverflow.ellipsis)
]);
}
Widget _buildPositionAndSpeed(TrainRecord record) {
final routeStr = record.route.trim();
final position = record.position.trim();
final speed = record.speed.trim();
final isValidRoute = routeStr.isNotEmpty &&
!routeStr.runes.every((r) => r == '*'.runes.first);
final isValidPosition = position.isNotEmpty &&
!position.runes
.every((r) => r == '-'.runes.first || r == '.'.runes.first) &&
position != "<NUL>";
final isValidSpeed = speed.isNotEmpty &&
!speed.runes
.every((r) => r == '*'.runes.first || r == '-'.runes.first) &&
speed != "NUL" &&
speed != "<NUL>";
if (!isValidRoute && !isValidPosition && !isValidSpeed)
return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child:
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
if (isValidRoute || isValidPosition)
Expanded(
child: Row(children: [
if (isValidRoute)
Flexible(
child: Text(routeStr,
style:
const TextStyle(fontSize: 16, color: Colors.white),
overflow: TextOverflow.ellipsis)),
if (isValidRoute && isValidPosition) const SizedBox(width: 4),
if (isValidPosition)
Flexible(
child: Text("$position K",
style:
const TextStyle(fontSize: 16, color: Colors.white),
overflow: TextOverflow.ellipsis))
])),
if (isValidSpeed)
Text("$speed km/h",
style: const TextStyle(fontSize: 16, color: Colors.white),
textAlign: TextAlign.right)
]));
}
Widget _buildExpandedContent(TrainRecord record) {
final position = _parsePosition(record.positionInfo);
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
if (position != null)
Column(children: [
const SizedBox(height: 8),
Container(
height: 220,
margin: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.grey[900]),
child: FlutterMap(
options:
MapOptions(initialCenter: position, initialZoom: 15.0),
children: [
TileLayer(
urlTemplate:
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'org.noxylva.lbjconsole'),
MarkerLayer(markers: [
Marker(
point: position,
width: 40,
height: 40,
child: Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: Colors.white, width: 2)),
child: const Icon(Icons.train,
color: Colors.white, size: 20)))
])
]))
])
]);
}
LatLng? _parsePosition(String? positionInfo) {
if (positionInfo == null || positionInfo.isEmpty || positionInfo == '<NUL>')
return null;
try {
final parts = positionInfo.trim().split(RegExp(r'\s+'));
if (parts.length >= 2) {
final lat = _parseDmsCoordinate(parts[0]);
final lng = _parseDmsCoordinate(parts[1]);
if (lat != null &&
lng != null &&
(lat.abs() > 0.001 || lng.abs() > 0.001)) {
return LatLng(lat, lng);
}
}
} catch (e) {}
return null;
}
double? _parseDmsCoordinate(String dmsStr) {
try {
final degreeIndex = dmsStr.indexOf('°');
if (degreeIndex == -1) return null;
final degrees = double.tryParse(dmsStr.substring(0, degreeIndex));
if (degrees == null) return null;
final minuteIndex = dmsStr.indexOf('');
if (minuteIndex == -1) return degrees;
final minutes =
double.tryParse(dmsStr.substring(degreeIndex + 1, minuteIndex));
if (minutes == null) return degrees;
return degrees + (minutes / 60.0);
} catch (e) {
return null;
}
}
}

View File

@@ -0,0 +1,398 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:lbjconsole/models/merged_record.dart';
import 'package:lbjconsole/models/train_record.dart';
import 'package:lbjconsole/screens/history_screen.dart';
import 'package:lbjconsole/screens/map_screen.dart';
import 'package:lbjconsole/screens/settings_screen.dart';
import 'package:lbjconsole/services/ble_service.dart';
import 'package:lbjconsole/services/database_service.dart';
import 'package:lbjconsole/services/notification_service.dart';
import 'package:lbjconsole/themes/app_theme.dart';
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
int _currentIndex = 0;
late final BLEService _bleService;
final NotificationService _notificationService = NotificationService();
StreamSubscription? _connectionSubscription;
StreamSubscription? _dataSubscription;
bool _isHistoryEditMode = false;
final GlobalKey<HistoryScreenState> _historyScreenKey =
GlobalKey<HistoryScreenState>();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_bleService = BLEService();
_bleService.initialize();
_initializeServices();
}
@override
void dispose() {
_connectionSubscription?.cancel();
_dataSubscription?.cancel();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_bleService.onAppResume();
}
}
Future<void> _initializeServices() async {
await _notificationService.initialize();
_connectionSubscription = _bleService.connectionStream.listen((_) {
if (mounted) setState(() {});
});
_dataSubscription = _bleService.dataStream.listen((record) {
_notificationService.showTrainNotification(record);
if (_historyScreenKey.currentState != null) {
_historyScreenKey.currentState!.loadRecords(scrollToTop: true);
}
});
}
void _showConnectionDialog() {
_bleService.setAutoConnectBlocked(true);
showDialog(
context: context,
barrierDismissible: true,
builder: (context) =>
_PixelPerfectBluetoothDialog(bleService: _bleService),
).then((_) {
_bleService.setAutoConnectBlocked(false);
if (!_bleService.isManualDisconnect) {
_bleService.ensureConnection();
}
});
}
AppBar _buildAppBar(BuildContext context) {
final historyState = _historyScreenKey.currentState;
final selectedCount = historyState?.getSelectedCount() ?? 0;
if (_currentIndex == 0 && _isHistoryEditMode) {
return AppBar(
backgroundColor: Theme.of(context).primaryColor,
leading: IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: _handleHistoryCancelSelection,
),
title: Text(
'已选择 $selectedCount',
style: const TextStyle(color: Colors.white, fontSize: 18),
),
actions: [
IconButton(
icon: const Icon(Icons.delete, color: Colors.white),
onPressed: selectedCount > 0 ? _handleHistoryDeleteSelected : null,
),
],
);
}
return AppBar(
backgroundColor: AppTheme.primaryBlack,
elevation: 0,
title: Text(
['列车记录', '位置地图', '设置'][_currentIndex],
style: const TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
),
centerTitle: false,
actions: [
Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: _bleService.isConnected ? Colors.green : Colors.red,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Text(_bleService.deviceStatus,
style: const TextStyle(color: Colors.white70)),
IconButton(
icon: const Icon(Icons.bluetooth, color: Colors.white),
onPressed: _showConnectionDialog,
),
],
),
],
);
}
void _handleHistoryEditModeChanged(bool isEditing) {
setState(() {
_isHistoryEditMode = isEditing;
if (!isEditing) {
_historyScreenKey.currentState?.clearSelection();
}
});
}
void _handleSelectionChanged() {
if (_isHistoryEditMode &&
(_historyScreenKey.currentState?.getSelectedCount() ?? 0) == 0) {
_handleHistoryCancelSelection();
} else {
setState(() {});
}
}
void _handleHistoryCancelSelection() {
_historyScreenKey.currentState?.setEditMode(false);
}
Future<void> _handleHistoryDeleteSelected() async {
final historyState = _historyScreenKey.currentState;
if (historyState == null || historyState.getSelectedCount() == 0) return;
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: Text('确定要删除选中的 ${historyState.getSelectedCount()} 条记录吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red, foregroundColor: Colors.white),
child: const Text('删除'),
),
],
),
);
if (confirmed == true) {
final idsToDelete = historyState.getSelectedRecordIds().toList();
await DatabaseService.instance.deleteRecords(idsToDelete);
historyState.setEditMode(false);
historyState.loadRecords(scrollToTop: false);
}
}
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: AppTheme.primaryBlack,
statusBarIconBrightness: Brightness.light,
systemNavigationBarIconBrightness: Brightness.light,
));
final pages = [
HistoryScreen(
key: _historyScreenKey,
onEditModeChanged: _handleHistoryEditModeChanged,
onSelectionChanged: _handleSelectionChanged,
),
const MapScreen(),
const SettingsScreen(),
];
return Scaffold(
backgroundColor: AppTheme.primaryBlack,
appBar: _buildAppBar(context),
body: IndexedStack(
index: _currentIndex,
children: pages,
),
bottomNavigationBar: NavigationBar(
backgroundColor: AppTheme.secondaryBlack,
indicatorColor: AppTheme.accentBlue.withOpacity(0.2),
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
if (_currentIndex == 2 && index == 0) {
_historyScreenKey.currentState?.loadRecords();
}
setState(() {
if (_isHistoryEditMode) _isHistoryEditMode = false;
_currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.directions_railway), label: '列车记录'),
NavigationDestination(icon: Icon(Icons.location_on), label: '位置地图'),
NavigationDestination(icon: Icon(Icons.settings), label: '设置'),
],
),
);
}
}
enum _ScanState { initial, scanning, finished }
class _PixelPerfectBluetoothDialog extends StatefulWidget {
final BLEService bleService;
const _PixelPerfectBluetoothDialog({required this.bleService});
@override
State<_PixelPerfectBluetoothDialog> createState() =>
_PixelPerfectBluetoothDialogState();
}
class _PixelPerfectBluetoothDialogState
extends State<_PixelPerfectBluetoothDialog> {
List<BluetoothDevice> _devices = [];
_ScanState _scanState = _ScanState.initial;
StreamSubscription? _connectionSubscription;
@override
void initState() {
super.initState();
_connectionSubscription = widget.bleService.connectionStream.listen((_) {
if (mounted) setState(() {});
});
if (!widget.bleService.isConnected) {
_startScan();
}
}
@override
void dispose() {
_connectionSubscription?.cancel();
super.dispose();
}
Future<void> _startScan() async {
if (_scanState == _ScanState.scanning) return;
if (mounted)
setState(() {
_devices.clear();
_scanState = _ScanState.scanning;
});
await widget.bleService.startScan(
timeout: const Duration(seconds: 8),
onScanResults: (devices) {
if (mounted) setState(() => _devices = devices);
},
);
if (mounted) setState(() => _scanState = _ScanState.finished);
}
Future<void> _connectToDevice(BluetoothDevice device) async {
Navigator.pop(context);
await widget.bleService.connectManually(device);
}
Future<void> _disconnect() async {
Navigator.pop(context);
await widget.bleService.disconnect();
}
@override
Widget build(BuildContext context) {
final isConnected = widget.bleService.isConnected;
return AlertDialog(
title: const Text('蓝牙设备'),
content: SizedBox(
width: double.maxFinite,
child: SingleChildScrollView(
child: isConnected
? _buildConnectedView(context, widget.bleService.connectedDevice)
: _buildDisconnectedView(context),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
],
);
}
Widget _buildConnectedView(BuildContext context, BluetoothDevice? device) {
return Column(mainAxisSize: MainAxisSize.min, children: [
const Icon(Icons.bluetooth_connected, size: 48, color: Colors.green),
const SizedBox(height: 16),
Text('设备已连接',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(device?.platformName ?? '未知设备', textAlign: TextAlign.center),
Text(device?.remoteId.str ?? '',
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _disconnect,
icon: const Icon(Icons.bluetooth_disabled),
label: const Text('断开连接'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red, foregroundColor: Colors.white))
]);
}
Widget _buildDisconnectedView(BuildContext context) {
return Column(mainAxisSize: MainAxisSize.min, children: [
ElevatedButton.icon(
onPressed: _scanState == _ScanState.scanning ? null : _startScan,
icon: _scanState == _ScanState.scanning
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2, color: Colors.white))
: const Icon(Icons.search),
label: Text(_scanState == _ScanState.scanning ? '扫描中...' : '扫描设备'),
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 40))),
const SizedBox(height: 16),
if (_scanState == _ScanState.finished && _devices.isNotEmpty)
_buildDeviceListView()
]);
}
Widget _buildDeviceListView() {
return SizedBox(
height: 200,
child: ListView.builder(
shrinkWrap: true,
itemCount: _devices.length,
itemBuilder: (context, index) {
final device = _devices[index];
return Card(
margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
leading: const Icon(Icons.bluetooth),
title: Text(device.platformName.isNotEmpty
? device.platformName
: '未知设备'),
subtitle: Text(device.remoteId.str),
onTap: () => _connectToDevice(device),
),
);
},
),
);
}
}

640
lib/screens/map_screen.dart Normal file
View File

@@ -0,0 +1,640 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:geolocator/geolocator.dart';
import 'package:lbjconsole/services/database_service.dart';
import 'package:lbjconsole/models/train_record.dart';
class MapScreen extends StatefulWidget {
const MapScreen({super.key});
@override
State<MapScreen> createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
final MapController _mapController = MapController();
final List<TrainRecord> _trainRecords = [];
bool _isLoading = true;
bool _railwayLayerVisible = true;
LatLng? _currentLocation;
LatLng? _lastTrainLocation;
LatLng? _userLocation;
double _currentZoom = 12.0;
double _currentRotation = 0.0;
bool _isMapInitialized = false;
bool _isFollowingLocation = false;
bool _isLocationPermissionGranted = false;
static const LatLng _defaultPosition = LatLng(39.9042, 116.4074);
@override
void initState() {
super.initState();
_initializeMap();
_loadTrainRecords();
_loadSettings();
_requestLocationPermission();
}
@override
void dispose() {
_saveSettings();
super.dispose();
}
Future<void> _initializeMap() async {}
Future<void> _requestLocationPermission() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return;
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
if (permission == LocationPermission.deniedForever) {
return;
}
setState(() {
_isLocationPermissionGranted = true;
});
_getCurrentLocation();
}
Future<void> _getCurrentLocation() async {
try {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
setState(() {
_userLocation = LatLng(position.latitude, position.longitude);
});
if (!_isMapInitialized && _userLocation != null) {
_mapController.move(_userLocation!, _currentZoom);
}
} catch (e) {}
}
Future<void> _loadSettings() async {
try {
final settings = await DatabaseService.instance.getAllSettings();
if (settings != null) {
setState(() {
_railwayLayerVisible =
(settings['mapRailwayLayerVisible'] as int?) == 1;
_currentZoom = (settings['mapZoomLevel'] as num?)?.toDouble() ?? 10.0;
_currentRotation =
(settings['mapRotation'] as num?)?.toDouble() ?? 0.0;
final lat = (settings['mapCenterLat'] as num?)?.toDouble();
final lon = (settings['mapCenterLon'] as num?)?.toDouble();
if (lat != null && lon != null) {
_currentLocation = LatLng(lat, lon);
}
});
}
} catch (e) {}
}
Future<void> _saveSettings() async {
try {
final center = _mapController.camera.center;
await DatabaseService.instance.updateSettings({
'mapRailwayLayerVisible': _railwayLayerVisible ? 1 : 0,
'mapZoomLevel': _currentZoom,
'mapCenterLat': center.latitude,
'mapCenterLon': center.longitude,
'mapRotation': _currentRotation,
});
} catch (e) {}
}
Future<void> _loadTrainRecords() async {
setState(() => _isLoading = true);
try {
final records = await DatabaseService.instance.getAllRecords();
setState(() {
_trainRecords.clear();
_trainRecords.addAll(records);
_isLoading = false;
if (_trainRecords.isNotEmpty) {
final lastRecord = _trainRecords.first;
final coords = lastRecord.getCoordinates();
final dmsCoords = _parseDmsCoordinate(lastRecord.positionInfo);
if (dmsCoords != null) {
_lastTrainLocation = dmsCoords;
} else if (coords['lat'] != 0.0 && coords['lng'] != 0.0) {
_lastTrainLocation = LatLng(coords['lat']!, coords['lng']!);
}
}
_initializeMapPosition();
});
} catch (e) {
setState(() => _isLoading = false);
}
}
void _initializeMapPosition() {
if (_isMapInitialized) return;
LatLng? targetLocation;
if (_currentLocation != null) {
targetLocation = _currentLocation;
} else if (_userLocation != null) {
targetLocation = _userLocation;
} else if (_lastTrainLocation != null) {
targetLocation = _lastTrainLocation;
} else {
targetLocation = _defaultPosition;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
_centerMap(targetLocation!, zoom: _currentZoom);
_isMapInitialized = true;
});
}
void _centerMap(LatLng location, {double? zoom}) {
_mapController.move(location, zoom ?? _currentZoom);
}
LatLng? _parseDmsCoordinate(String? positionInfo) {
if (positionInfo == null ||
positionInfo.isEmpty ||
positionInfo == '<NUL>') {
return null;
}
try {
final parts = positionInfo.trim().split(' ');
if (parts.length >= 2) {
final latStr = parts[0];
final lngStr = parts[1];
final lat = _parseDmsString(latStr);
final lng = _parseDmsString(lngStr);
if (lat != null &&
lng != null &&
(lat.abs() > 0.001 || lng.abs() > 0.001)) {
return LatLng(lat, lng);
}
}
} catch (e) {
print('解析DMS坐标失败: $e');
}
return null;
}
double? _parseDmsString(String dmsStr) {
try {
final degreeIndex = dmsStr.indexOf('°');
if (degreeIndex == -1) return null;
final degrees = double.tryParse(dmsStr.substring(0, degreeIndex));
if (degrees == null) return null;
final minuteIndex = dmsStr.indexOf('');
if (minuteIndex == -1) return degrees;
final minutes =
double.tryParse(dmsStr.substring(degreeIndex + 1, minuteIndex));
if (minutes == null) return degrees;
return degrees + (minutes / 60.0);
} catch (e) {
return null;
}
}
List<TrainRecord> _getValidRecords() {
return _trainRecords.where((record) {
final coords = record.getCoordinates();
return coords['lat'] != 0.0 && coords['lng'] != 0.0;
}).toList();
}
List<TrainRecord> _getValidDmsRecords() {
return _trainRecords.where((record) {
return _parseDmsCoordinate(record.positionInfo) != null;
}).toList();
}
List<Marker> _buildTrainMarkers() {
final markers = <Marker>[];
final validRecords = [..._getValidRecords(), ..._getValidDmsRecords()];
for (final record in validRecords) {
LatLng? position;
final dmsPosition = _parseDmsCoordinate(record.positionInfo);
if (dmsPosition != null) {
position = dmsPosition;
} else {
final coords = record.getCoordinates();
if (coords['lat'] != 0.0 && coords['lng'] != 0.0) {
position = LatLng(coords['lat']!, coords['lng']!);
}
}
if (position != null) {
final trainDisplay =
record.fullTrainNumber.isEmpty ? "未知列车" : record.fullTrainNumber;
markers.add(
Marker(
point: position,
width: 80,
height: 60,
child: GestureDetector(
onTap: () => position != null
? _showTrainDetailsDialog(record, position)
: null,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: Colors.white, width: 2),
),
child: const Icon(
Icons.train,
color: Colors.white,
size: 18,
),
),
const SizedBox(height: 2),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(2),
),
child: Text(
trainDisplay,
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
),
),
);
}
}
return markers;
}
void _centerToMyLocation() {
_centerMap(_lastTrainLocation ?? _defaultPosition, zoom: 15.0);
}
void _centerToLastTrain() {
if (_trainRecords.isNotEmpty) {
final lastRecord = _trainRecords.first;
final coords = lastRecord.getCoordinates();
final dmsCoords = _parseDmsCoordinate(lastRecord.positionInfo);
LatLng? targetPosition;
if (dmsCoords != null) {
targetPosition = dmsCoords;
} else if (coords['lat'] != 0.0 && coords['lng'] != 0.0) {
targetPosition = LatLng(coords['lat']!, coords['lng']!);
}
if (targetPosition != null) {
_centerMap(targetPosition, zoom: 15.0);
}
}
}
void _showTrainDetailsDialog(TrainRecord record, LatLng position) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
builder: (context) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(28)),
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 4,
height: 24,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
record.fullTrainNumber.isEmpty
? "未知列车"
: record.fullTrainNumber,
style:
Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 16),
Container(
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.3),
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildMaterial3DetailRow(
context, "时间", record.formattedTime),
_buildMaterial3DetailRow(
context, "日期", record.formattedDate),
_buildMaterial3DetailRow(
context, "类型", record.trainType),
_buildMaterial3DetailRow(
context, "速度", "${record.speed} km/h"),
_buildMaterial3DetailRow(
context, "位置", record.position),
_buildMaterial3DetailRow(context, "路线", record.route),
_buildMaterial3DetailRow(
context, "机车", "${record.locoType}-${record.loco}"),
_buildMaterial3DetailRow(context, "坐标",
"${position.latitude.toStringAsFixed(4)}, ${position.longitude.toStringAsFixed(4)}"),
],
),
),
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: FilledButton.tonal(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton(
onPressed: () {
Navigator.pop(context);
_centerMap(position, zoom: 17.0);
},
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.my_location, size: 16),
SizedBox(width: 8),
Text('居中查看'),
],
),
),
),
],
),
],
),
),
);
},
);
}
Widget _buildDetailRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 80,
child: Text(
label,
style: const TextStyle(color: Colors.grey, fontSize: 14),
),
),
Expanded(
child: Text(
value.isEmpty ? "未知" : value,
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
],
),
);
}
Widget _buildMaterial3DetailRow(
BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 60,
child: Text(
label,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
),
Expanded(
child: Text(
value.isEmpty ? "未知" : value,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final markers = _buildTrainMarkers();
if (_userLocation != null) {
markers.add(
Marker(
point: _userLocation!,
width: 40,
height: 40,
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
child: const Icon(
Icons.my_location,
color: Colors.white,
size: 20,
),
),
),
);
}
return Scaffold(
backgroundColor: const Color(0xFF121212),
body: Stack(
children: [
FlutterMap(
mapController: _mapController,
options: MapOptions(
initialCenter: _lastTrainLocation ?? _defaultPosition,
initialZoom: _currentZoom,
initialRotation: _currentRotation,
minZoom: 4.0,
maxZoom: 18.0,
onPositionChanged: (MapCamera camera, bool hasGesture) {
if (hasGesture) {
setState(() {
_currentLocation = camera.center;
_currentZoom = camera.zoom;
_currentRotation = camera.rotation;
});
_saveSettings();
}
},
onTap: (_, point) {
for (final record in _trainRecords) {
final coords = record.getCoordinates();
final dmsCoords = _parseDmsCoordinate(record.positionInfo);
LatLng? recordPosition;
if (dmsCoords != null) {
recordPosition = dmsCoords;
} else if (coords['lat'] != 0.0 && coords['lng'] != 0.0) {
recordPosition = LatLng(coords['lat']!, coords['lng']!);
}
if (recordPosition != null) {
final distance = const Distance()
.as(LengthUnit.Meter, recordPosition, point);
if (distance < 50) {
_showTrainDetailsDialog(record, recordPosition);
break;
}
}
}
},
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'org.noxylva.lbjconsole',
),
if (_railwayLayerVisible)
TileLayer(
urlTemplate:
'https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
subdomains: const ['a', 'b', 'c'],
userAgentPackageName: 'org.noxylva.lbjconsole',
),
MarkerLayer(
markers: markers,
),
],
),
if (_isLoading)
const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF007ACC)),
),
),
Positioned(
right: 16,
top: 40,
child: Column(
children: [
FloatingActionButton.small(
heroTag: 'railwayLayer',
backgroundColor: const Color(0xFF1E1E1E),
onPressed: () {
setState(() {
_railwayLayerVisible = !_railwayLayerVisible;
});
_saveSettings();
},
child: Icon(
_railwayLayerVisible ? Icons.layers : Icons.layers_outlined,
color: Colors.white,
),
),
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'myLocation',
backgroundColor: const Color(0xFF1E1E1E),
onPressed: () {
_getCurrentLocation();
if (_userLocation != null) {
_centerMap(_userLocation!, zoom: 15.0);
}
},
child: const Icon(Icons.my_location, color: Colors.white),
),
const SizedBox(height: 8),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,805 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:lbjconsole/models/merged_record.dart';
import 'package:lbjconsole/services/database_service.dart';
import 'package:lbjconsole/services/ble_service.dart';
import 'package:lbjconsole/themes/app_theme.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:package_info_plus/package_info_plus.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
late DatabaseService _databaseService;
late TextEditingController _deviceNameController;
String _deviceName = '';
bool _backgroundServiceEnabled = false;
bool _notificationsEnabled = true;
int _recordCount = 0;
bool _mergeRecordsEnabled = false;
GroupBy _groupBy = GroupBy.trainAndLoco;
TimeWindow _timeWindow = TimeWindow.unlimited;
@override
void initState() {
super.initState();
_databaseService = DatabaseService.instance;
_deviceNameController = TextEditingController();
_loadSettings();
_loadRecordCount();
}
@override
void dispose() {
_deviceNameController.dispose();
super.dispose();
}
Future<void> _loadSettings() async {
final settingsMap = await _databaseService.getAllSettings() ?? {};
final settings = MergeSettings.fromMap(settingsMap);
if (mounted) {
setState(() {
_deviceName = settingsMap['deviceName'] ?? 'LBJReceiver';
_deviceNameController.text = _deviceName;
_backgroundServiceEnabled =
(settingsMap['backgroundServiceEnabled'] ?? 0) == 1;
_notificationsEnabled = (settingsMap['notificationEnabled'] ?? 1) == 1;
_mergeRecordsEnabled = settings.enabled;
_groupBy = settings.groupBy;
_timeWindow = settings.timeWindow;
});
}
}
Future<void> _loadRecordCount() async {
final count = await _databaseService.getRecordCount();
if (mounted) {
setState(() {
_recordCount = count;
});
}
}
Future<void> _saveSettings() async {
await _databaseService.updateSettings({
'deviceName': _deviceName,
'backgroundServiceEnabled': _backgroundServiceEnabled ? 1 : 0,
'notificationEnabled': _notificationsEnabled ? 1 : 0,
'mergeRecordsEnabled': _mergeRecordsEnabled ? 1 : 0,
'groupBy': _groupBy.name,
'timeWindow': _timeWindow.name,
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildBluetoothSettings(),
const SizedBox(height: 20),
_buildAppSettings(),
const SizedBox(height: 20),
_buildMergeSettings(),
const SizedBox(height: 20),
_buildDataManagement(),
const SizedBox(height: 20),
_buildAboutSection(),
],
),
);
}
Widget _buildBluetoothSettings() {
return Card(
color: AppTheme.tertiaryBlack,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.bluetooth,
color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
Text('蓝牙设备', style: AppTheme.titleMedium),
],
),
const SizedBox(height: 16),
TextField(
controller: _deviceNameController,
decoration: InputDecoration(
labelText: '设备名称 (用于自动连接)',
hintText: '输入设备名称',
labelStyle: const TextStyle(color: Colors.white70),
hintStyle: const TextStyle(color: Colors.white54),
border: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.white54),
borderRadius: BorderRadius.circular(12.0),
),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.white54),
borderRadius: BorderRadius.circular(12.0),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).colorScheme.primary),
borderRadius: BorderRadius.circular(12.0),
),
),
style: const TextStyle(color: Colors.white),
onChanged: (value) {
setState(() {
_deviceName = value;
});
_saveSettings();
},
),
],
),
),
);
}
Widget _buildAppSettings() {
return Card(
color: AppTheme.tertiaryBlack,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.settings,
color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
Text('应用设置', style: AppTheme.titleMedium),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('后台保活服务', style: AppTheme.bodyLarge),
Text('保持应用在后台运行', style: AppTheme.caption),
],
),
Switch(
value: _backgroundServiceEnabled,
onChanged: (value) {
setState(() {
_backgroundServiceEnabled = value;
});
_saveSettings();
},
activeColor: Theme.of(context).colorScheme.primary,
),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('LBJ消息通知', style: AppTheme.bodyLarge),
Text('接收LBJ消息通知', style: AppTheme.caption),
],
),
Switch(
value: _notificationsEnabled,
onChanged: (value) {
setState(() {
_notificationsEnabled = value;
});
_saveSettings();
},
activeColor: Theme.of(context).colorScheme.primary,
),
],
),
],
),
),
);
}
Widget _buildMergeSettings() {
return Card(
color: AppTheme.tertiaryBlack,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.merge_type,
color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
Text('记录合并', style: AppTheme.titleMedium),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('启用记录合并', style: AppTheme.bodyLarge),
Text('合并相同内容的LBJ记录', style: AppTheme.caption),
],
),
Switch(
value: _mergeRecordsEnabled,
onChanged: (value) {
setState(() {
_mergeRecordsEnabled = value;
});
_saveSettings();
},
activeColor: Theme.of(context).colorScheme.primary,
),
],
),
Visibility(
visible: _mergeRecordsEnabled,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
Text('分组方式', style: AppTheme.bodyLarge),
const SizedBox(height: 8),
DropdownButtonFormField<GroupBy>(
value: _groupBy,
items: [
DropdownMenuItem(
value: GroupBy.trainOnly,
child: Text('仅车次号', style: AppTheme.bodyMedium)),
DropdownMenuItem(
value: GroupBy.locoOnly,
child: Text('仅机车号', style: AppTheme.bodyMedium)),
DropdownMenuItem(
value: GroupBy.trainOrLoco,
child: Text('车次号或机车号', style: AppTheme.bodyMedium)),
DropdownMenuItem(
value: GroupBy.trainAndLoco,
child: Text('车次号与机车号', style: AppTheme.bodyMedium)),
],
onChanged: (value) {
if (value != null) {
setState(() {
_groupBy = value;
});
_saveSettings();
}
},
decoration: InputDecoration(
filled: true,
fillColor: AppTheme.secondaryBlack,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: BorderSide.none,
),
),
dropdownColor: AppTheme.secondaryBlack,
style: AppTheme.bodyMedium,
),
const SizedBox(height: 16),
Text('时间窗口', style: AppTheme.bodyLarge),
const SizedBox(height: 8),
DropdownButtonFormField<TimeWindow>(
value: _timeWindow,
items: [
DropdownMenuItem(
value: TimeWindow.oneHour,
child: Text('1小时内', style: AppTheme.bodyMedium)),
DropdownMenuItem(
value: TimeWindow.twoHours,
child: Text('2小时内', style: AppTheme.bodyMedium)),
DropdownMenuItem(
value: TimeWindow.sixHours,
child: Text('6小时内', style: AppTheme.bodyMedium)),
DropdownMenuItem(
value: TimeWindow.twelveHours,
child: Text('12小时内', style: AppTheme.bodyMedium)),
DropdownMenuItem(
value: TimeWindow.oneDay,
child: Text('24小时内', style: AppTheme.bodyMedium)),
DropdownMenuItem(
value: TimeWindow.unlimited,
child: Text('不限时间', style: AppTheme.bodyMedium)),
],
onChanged: (value) {
if (value != null) {
setState(() {
_timeWindow = value;
});
_saveSettings();
}
},
decoration: InputDecoration(
filled: true,
fillColor: AppTheme.secondaryBlack,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: BorderSide.none,
),
),
dropdownColor: AppTheme.secondaryBlack,
style: AppTheme.bodyMedium,
),
],
),
),
],
),
),
);
}
Widget _buildDataManagement() {
return Card(
color: AppTheme.tertiaryBlack,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.share, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
Text('数据导出', style: AppTheme.titleMedium),
],
),
const SizedBox(height: 16),
_buildActionButton(
icon: Icons.download,
title: '导出数据',
subtitle: '将记录导出为JSON文件',
onTap: _exportData,
),
const SizedBox(height: 12),
_buildActionButton(
icon: Icons.file_download,
title: '导入数据',
subtitle: '从JSON文件导入记录和设置',
onTap: _importData,
),
const SizedBox(height: 12),
_buildActionButton(
icon: Icons.clear_all,
title: '清空数据',
subtitle: '删除所有记录和设置',
onTap: _clearAllData,
isDestructive: true,
),
],
),
),
);
}
Widget _buildActionButton({
required IconData icon,
required String title,
required String subtitle,
required VoidCallback onTap,
bool isDestructive = false,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12.0),
child: Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: isDestructive
? Colors.red.withOpacity(0.1)
: AppTheme.secondaryBlack,
borderRadius: BorderRadius.circular(12.0),
border: Border.all(
color: isDestructive
? Colors.red.withOpacity(0.3)
: Colors.transparent,
width: 1,
),
),
child: Row(
children: [
Icon(
icon,
color: isDestructive
? Colors.red
: Theme.of(context).colorScheme.primary,
size: 24,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppTheme.bodyLarge.copyWith(
color: isDestructive ? Colors.red : Colors.white,
),
),
Text(
subtitle,
style: AppTheme.caption,
),
],
),
),
Icon(
Icons.chevron_right,
color: Colors.white54,
size: 20,
),
],
),
),
);
}
String _formatFileSize(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
return '${(bytes / 1024 / 1024).toStringAsFixed(1)} MB';
}
String _formatDateTime(DateTime dateTime) {
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} '
'${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
}
Future<String> _getAppVersion() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
return 'v${packageInfo.version}';
} catch (e) {
return '';
}
}
Future<String?> _selectDirectory() async {
try {
// 使用文件选择器选择目录
final directory = await FilePicker.platform.getDirectoryPath(
dialogTitle: '选择导出位置',
lockParentWindow: true,
);
return directory;
} catch (e) {
// 如果文件选择器失败,使用默认的文档目录
try {
final documentsDir = await getApplicationDocumentsDirectory();
final exportDir = Directory(path.join(documentsDir.path, 'LBJ_Exports'));
if (!await exportDir.exists()) {
await exportDir.create(recursive: true);
}
return exportDir.path;
} catch (e) {
return null;
}
}
}
Future<void> _exportData() async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
try {
// 让用户选择保存位置
final fileName =
'LBJ_Console_${DateTime.now().year}${DateTime.now().month.toString().padLeft(2, '0')}${DateTime.now().day.toString().padLeft(2, '0')}.json';
String? selectedDirectory = await _selectDirectory();
if (selectedDirectory == null) return;
final filePath = path.join(selectedDirectory, fileName);
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AlertDialog(
content: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 16),
Text('正在导出数据...'),
],
),
),
);
try {
final exportedPath = await _databaseService.exportDataAsJson(customPath: filePath);
Navigator.pop(context);
if (exportedPath != null) {
final file = File(exportedPath);
final fileName = file.path.split(Platform.pathSeparator).last;
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('数据已导出到:$fileName'),
action: SnackBarAction(
label: '查看',
onPressed: () async {
// 打开文件所在目录
try {
final directory = file.parent;
await Process.run('explorer', [directory.path]);
} catch (e) {
// 如果无法打开目录,显示路径
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('文件路径:${file.path}'),
),
);
}
},
),
),
);
} else {
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Text('导出失败'),
),
);
}
} catch (e) {
Navigator.pop(context);
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('导出错误:$e'),
),
);
}
} catch (e) {
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('选择目录错误:$e'),
),
);
}
}
Future<void> _importData() async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('导入数据'),
content: const Text('导入将替换所有现有数据,是否继续?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('继续'),
),
],
),
);
if (result != true) return;
final resultFile = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
if (resultFile == null) return;
final selectedFile = resultFile.files.single.path;
if (selectedFile == null) return;
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AlertDialog(
content: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 16),
Text('正在导入数据...'),
],
),
),
);
try {
final success = await _databaseService.importDataFromJson(selectedFile);
Navigator.pop(context);
if (success) {
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Text('数据导入成功'),
),
);
await _loadSettings();
await _loadRecordCount();
setState(() {});
} else {
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Text('数据导入失败'),
),
);
}
} catch (e) {
Navigator.pop(context);
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('导入错误:$e'),
),
);
}
}
Future<void> _clearAllData() async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('清空数据'),
content: const Text('此操作将删除所有记录和设置,无法撤销。是否继续?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('确认清空'),
),
],
),
);
if (result != true) return;
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AlertDialog(
content: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 16),
Text('正在清空数据...'),
],
),
),
);
try {
await _databaseService.deleteAllRecords();
await _databaseService.updateSettings({
'deviceName': 'LBJReceiver',
'backgroundServiceEnabled': 0,
'notificationEnabled': 1,
'mergeRecordsEnabled': 0,
'groupBy': 'trainAndLoco',
'timeWindow': 'unlimited',
});
Navigator.pop(context);
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Text('数据已清空'),
),
);
await _loadSettings();
await _loadRecordCount();
setState(() {});
} catch (e) {
Navigator.pop(context);
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('清空错误:$e'),
),
);
}
}
Widget _buildAboutSection() {
return Card(
color: AppTheme.tertiaryBlack,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.info, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
Text('关于', style: AppTheme.titleMedium),
],
),
const SizedBox(height: 16),
Text('LBJ Console', style: AppTheme.titleMedium),
const SizedBox(height: 8),
FutureBuilder<String>(
future: _getAppVersion(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!, style: AppTheme.bodyMedium);
} else {
return const Text('v0.1.3-flutter', style: AppTheme.bodyMedium);
}
},
),
const SizedBox(height: 16),
GestureDetector(
onTap: () async {
final url = Uri.parse('https://github.com/undef-i/LBJConsole');
if (await canLaunchUrl(url)) {
await launchUrl(url);
}
},
child: Text(
'https://github.com/undef-i/LBJConsole',
style: AppTheme.caption,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,361 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:lbjconsole/models/train_record.dart';
import 'package:lbjconsole/services/database_service.dart';
class BLEService {
static final BLEService _instance = BLEService._internal();
factory BLEService() => _instance;
BLEService._internal();
static const String TAG = "LBJ_BT_FLUTTER";
static final Guid serviceUuid = Guid("0000ffe0-0000-1000-8000-00805f9b34fb");
static final Guid charUuid = Guid("0000ffe1-0000-1000-8000-00805f9b34fb");
BluetoothDevice? _connectedDevice;
BluetoothCharacteristic? _characteristic;
StreamSubscription<List<int>>? _valueSubscription;
StreamSubscription<BluetoothConnectionState>? _connectionStateSubscription;
StreamSubscription<List<ScanResult>>? _scanResultsSubscription;
final StreamController<String> _statusController =
StreamController<String>.broadcast();
final StreamController<TrainRecord> _dataController =
StreamController<TrainRecord>.broadcast();
final StreamController<bool> _connectionController =
StreamController<bool>.broadcast();
Stream<String> get statusStream => _statusController.stream;
Stream<TrainRecord> get dataStream => _dataController.stream;
Stream<bool> get connectionStream => _connectionController.stream;
String _deviceStatus = "未连接";
String? _lastKnownDeviceAddress;
String _targetDeviceName = "LBJReceiver";
bool _isConnecting = false;
bool _isManualDisconnect = false;
bool _isAutoConnectBlocked = false;
Timer? _heartbeatTimer;
final StringBuffer _dataBuffer = StringBuffer();
void initialize() {
_loadSettings();
FlutterBluePlus.adapterState.listen((state) {
if (state == BluetoothAdapterState.on) {
ensureConnection();
} else {
_updateConnectionState(false, "蓝牙已关闭");
stopScan();
}
});
_startHeartbeat();
}
void _startHeartbeat() {
_heartbeatTimer?.cancel();
_heartbeatTimer = Timer.periodic(const Duration(seconds: 7), (timer) {
ensureConnection();
});
}
Future<void> _loadSettings() async {
try {
final settings = await DatabaseService.instance.getAllSettings();
if (settings != null) {
_targetDeviceName = settings['deviceName'] ?? 'LBJReceiver';
}
} catch (e) {
}
}
void ensureConnection() {
if (isConnected || _isConnecting) {
return;
}
_tryReconnectDirectly();
}
Future<void> _tryReconnectDirectly() async {
if (_lastKnownDeviceAddress == null) {
startScan();
return;
}
_isConnecting = true;
_statusController.add("正在重连...");
try {
final connected = await FlutterBluePlus.connectedSystemDevices;
final matchingDevices =
connected.where((d) => d.remoteId.str == _lastKnownDeviceAddress);
BluetoothDevice? target =
matchingDevices.isNotEmpty ? matchingDevices.first : null;
if (target != null) {
await connect(target);
} else {
startScan();
_isConnecting = false;
}
} catch (e) {
startScan();
_isConnecting = false;
}
}
Future<void> startScan({
String? targetName,
Duration? timeout,
Function(List<BluetoothDevice>)? onScanResults,
}) async {
if (FlutterBluePlus.isScanningNow) {
return;
}
_targetDeviceName = targetName ?? _targetDeviceName;
_statusController.add("正在扫描...");
_scanResultsSubscription?.cancel();
_scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
final allFoundDevices = results.map((r) => r.device).toList();
final filteredDevices = allFoundDevices.where((device) {
if (_targetDeviceName.isEmpty) return true;
return device.platformName.toLowerCase() ==
_targetDeviceName.toLowerCase();
}).toList();
onScanResults?.call(filteredDevices);
if (isConnected ||
_isConnecting ||
_isManualDisconnect ||
_isAutoConnectBlocked) return;
for (var device in allFoundDevices) {
if (_shouldAutoConnectTo(device)) {
stopScan();
connect(device);
break;
}
}
});
try {
await FlutterBluePlus.startScan(timeout: timeout);
} catch (e) {
_statusController.add("扫描失败");
}
}
bool _shouldAutoConnectTo(BluetoothDevice device) {
final deviceName = device.platformName;
final deviceAddress = device.remoteId.str;
if (_targetDeviceName.isNotEmpty &&
deviceName.toLowerCase() == _targetDeviceName.toLowerCase())
return true;
if (_lastKnownDeviceAddress != null &&
_lastKnownDeviceAddress == deviceAddress) return true;
return false;
}
Future<void> stopScan() async {
await FlutterBluePlus.stopScan();
_scanResultsSubscription?.cancel();
}
Future<void> connect(BluetoothDevice device) async {
if (isConnected) return;
_isConnecting = true;
_isManualDisconnect = false;
_statusController.add("正在连接: ${device.platformName}");
try {
_connectionStateSubscription?.cancel();
_connectionStateSubscription = device.connectionState.listen((state) {
if (state == BluetoothConnectionState.disconnected) {
_onDisconnected();
}
});
await device.connect(timeout: const Duration(seconds: 15));
await _onConnected(device);
} catch (e) {
_onDisconnected();
}
}
Future<void> _onConnected(BluetoothDevice device) async {
_connectedDevice = device;
_lastKnownDeviceAddress = device.remoteId.str;
await _discoverServicesAndSetupNotifications(device);
}
void _onDisconnected() {
final wasConnected = isConnected;
_updateConnectionState(false, "连接已断开");
_connectionStateSubscription?.cancel();
if (wasConnected && !_isManualDisconnect) {
ensureConnection();
}
_isConnecting = false;
}
Future<void> _discoverServicesAndSetupNotifications(
BluetoothDevice device) async {
try {
final services = await device.discoverServices();
for (var service in services) {
if (service.uuid == serviceUuid) {
for (var char in service.characteristics) {
if (char.uuid == charUuid) {
_characteristic = char;
await device.requestMtu(512);
await char.setNotifyValue(true);
_valueSubscription = char.lastValueStream.listen(_onDataReceived);
_updateConnectionState(true, "已连接");
_isConnecting = false;
return;
}
}
}
}
await device.disconnect();
} catch (e) {
await device.disconnect();
}
}
Future<void> connectManually(BluetoothDevice device) async {
_isManualDisconnect = false;
_isAutoConnectBlocked = false;
stopScan();
await connect(device);
}
Future<void> disconnect() async {
_isManualDisconnect = true;
stopScan();
await _connectionStateSubscription?.cancel();
await _valueSubscription?.cancel();
if (_connectedDevice != null) {
await _connectedDevice!.disconnect();
}
_onDisconnected();
}
void _onDataReceived(List<int> value) {
if (value.isEmpty) return;
try {
final data = utf8.decode(value);
_dataBuffer.write(data);
_processDataBuffer();
} catch (e) {}
}
void _processDataBuffer() {
String bufferContent = _dataBuffer.toString();
if (bufferContent.isEmpty) return;
int firstBrace = bufferContent.indexOf('{');
if (firstBrace == -1) {
_dataBuffer.clear();
return;
}
bufferContent = bufferContent.substring(firstBrace);
int braceCount = 0;
int lastValidJsonEnd = -1;
for (int i = 0; i < bufferContent.length; i++) {
if (bufferContent[i] == '{') {
braceCount++;
} else if (bufferContent[i] == '}') {
braceCount--;
}
if (braceCount == 0 && i > 0) {
lastValidJsonEnd = i;
String jsonToParse = bufferContent.substring(0, lastValidJsonEnd + 1);
_parseAndNotify(jsonToParse);
bufferContent = bufferContent.substring(lastValidJsonEnd + 1);
i = -1;
firstBrace = bufferContent.indexOf('{');
if (firstBrace != -1) {
bufferContent = bufferContent.substring(firstBrace);
} else {
break;
}
}
}
_dataBuffer.clear();
if (braceCount > 0) {
_dataBuffer.write(bufferContent);
}
}
void _parseAndNotify(String jsonData) {
try {
final decodedJson = jsonDecode(jsonData);
if (decodedJson is Map<String, dynamic>) {
final now = DateTime.now();
final recordData = Map<String, dynamic>.from(decodedJson);
recordData['uniqueId'] =
'${now.millisecondsSinceEpoch}_${Random().nextInt(9999)}';
recordData['receivedTimestamp'] = now.millisecondsSinceEpoch;
final trainRecord = TrainRecord.fromJson(recordData);
_dataController.add(trainRecord);
DatabaseService.instance.insertRecord(trainRecord);
}
} catch (e) {
print("$TAG: JSON Decode Error: $e, Data: $jsonData");
}
}
void _updateConnectionState(bool connected, String status) {
if (connected) {
_deviceStatus = "已连接";
} else {
_deviceStatus = status;
_connectedDevice = null;
_characteristic = null;
}
_statusController.add(_deviceStatus);
_connectionController.add(connected);
}
void onAppResume() {
ensureConnection();
}
void setAutoConnectBlocked(bool blocked) {
_isAutoConnectBlocked = blocked;
}
bool get isConnected => _connectedDevice != null;
String get deviceStatus => _deviceStatus;
String? get deviceAddress => _connectedDevice?.remoteId.str;
bool get isScanning => FlutterBluePlus.isScanningNow;
BluetoothDevice? get connectedDevice => _connectedDevice;
bool get isManualDisconnect => _isManualDisconnect;
void dispose() {
_heartbeatTimer?.cancel();
disconnect();
_statusController.close();
_dataController.close();
_connectionController.close();
}
}

View File

@@ -0,0 +1,320 @@
import 'dart:async';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:convert';
import 'package:lbjconsole/models/train_record.dart';
class DatabaseService {
static final DatabaseService instance = DatabaseService._internal();
factory DatabaseService() => instance;
DatabaseService._internal();
static const String _databaseName = 'train_database';
static const _databaseVersion = 1;
static const String trainRecordsTable = 'train_records';
static const String appSettingsTable = 'app_settings';
Database? _database;
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
final directory = await getApplicationDocumentsDirectory();
final path = join(directory.path, _databaseName);
return await openDatabase(
path,
version: _databaseVersion,
onCreate: _onCreate,
);
}
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS $trainRecordsTable (
uniqueId TEXT PRIMARY KEY,
timestamp INTEGER NOT NULL,
receivedTimestamp INTEGER NOT NULL,
train TEXT NOT NULL,
direction INTEGER NOT NULL,
speed TEXT NOT NULL,
position TEXT NOT NULL,
time TEXT NOT NULL,
loco TEXT NOT NULL,
locoType TEXT NOT NULL,
lbjClass TEXT NOT NULL,
route TEXT NOT NULL,
positionInfo TEXT NOT NULL,
rssi REAL NOT NULL
)
''');
await db.execute('''
CREATE TABLE IF NOT EXISTS $appSettingsTable (
id INTEGER PRIMARY KEY,
deviceName TEXT NOT NULL DEFAULT 'LBJReceiver',
currentTab INTEGER NOT NULL DEFAULT 0,
historyEditMode INTEGER NOT NULL DEFAULT 0,
historySelectedRecords TEXT NOT NULL DEFAULT '',
historyExpandedStates TEXT NOT NULL DEFAULT '',
historyScrollPosition INTEGER NOT NULL DEFAULT 0,
historyScrollOffset INTEGER NOT NULL DEFAULT 0,
settingsScrollPosition INTEGER NOT NULL DEFAULT 0,
mapCenterLat REAL,
mapCenterLon REAL,
mapZoomLevel REAL NOT NULL DEFAULT 10.0,
mapRailwayLayerVisible INTEGER NOT NULL DEFAULT 1,
mapRotation REAL NOT NULL DEFAULT 0.0,
specifiedDeviceAddress TEXT,
searchOrderList TEXT NOT NULL DEFAULT '',
autoConnectEnabled INTEGER NOT NULL DEFAULT 1,
backgroundServiceEnabled INTEGER NOT NULL DEFAULT 0,
notificationEnabled INTEGER NOT NULL DEFAULT 0,
mergeRecordsEnabled INTEGER NOT NULL DEFAULT 0,
groupBy TEXT NOT NULL DEFAULT 'trainAndLoco',
timeWindow TEXT NOT NULL DEFAULT 'unlimited'
)
''');
await db.insert(appSettingsTable, {
'id': 1,
'deviceName': 'LBJReceiver',
'currentTab': 0,
'historyEditMode': 0,
'historySelectedRecords': '',
'historyExpandedStates': '',
'historyScrollPosition': 0,
'historyScrollOffset': 0,
'settingsScrollPosition': 0,
'mapZoomLevel': 10.0,
'mapRailwayLayerVisible': 1,
'mapRotation': 0.0,
'searchOrderList': '',
'autoConnectEnabled': 1,
'backgroundServiceEnabled': 0,
'notificationEnabled': 0,
'mergeRecordsEnabled': 0,
'groupBy': 'trainAndLoco',
'timeWindow': 'unlimited',
});
}
Future<int> insertRecord(TrainRecord record) async {
final db = await database;
return await db.insert(
trainRecordsTable,
record.toDatabaseJson(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<List<TrainRecord>> getAllRecords() async {
final db = await database;
final result = await db.query(
trainRecordsTable,
orderBy: 'timestamp DESC',
);
return result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
}
Future<int> deleteRecord(String uniqueId) async {
final db = await database;
return await db.delete(
trainRecordsTable,
where: 'uniqueId = ?',
whereArgs: [uniqueId],
);
}
Future<int> deleteAllRecords() async {
final db = await database;
return await db.delete(trainRecordsTable);
}
Future<int> getRecordCount() async {
final db = await database;
final result = await db.rawQuery('SELECT COUNT(*) FROM $trainRecordsTable');
return Sqflite.firstIntValue(result) ?? 0;
}
Future<TrainRecord?> getLatestRecord() async {
final db = await database;
final result = await db.query(
trainRecordsTable,
orderBy: 'timestamp DESC',
limit: 1,
);
if (result.isNotEmpty) {
return TrainRecord.fromDatabaseJson(result.first);
}
return null;
}
Future<Map<String, dynamic>?> getAllSettings() async {
final db = await database;
try {
final result = await db.query(
appSettingsTable,
where: 'id = 1',
);
if (result.isEmpty) return null;
return result.first;
} catch (e) {
return null;
}
}
Future<int> updateSettings(Map<String, dynamic> settings) async {
final db = await database;
return await db.update(
appSettingsTable,
settings,
where: 'id = 1',
);
}
Future<int> setSetting(String key, dynamic value) async {
final db = await database;
return await db.update(
appSettingsTable,
{key: value},
where: 'id = 1',
);
}
Future<List<String>> getSearchOrderList() async {
final settings = await getAllSettings();
if (settings != null && settings['searchOrderList'] != null) {
final listString = settings['searchOrderList'] as String;
if (listString.isNotEmpty) {
return listString.split(',');
}
}
return [];
}
Future<int> updateSearchOrderList(List<String> orderList) async {
return await setSetting('searchOrderList', orderList.join(','));
}
Future<Map<String, dynamic>> getDatabaseInfo() async {
final db = await database;
final count = await getRecordCount();
final settings = await getAllSettings();
return {
'databaseVersion': _databaseVersion,
'trainRecordCount': count,
'appSettings': settings,
'path': db.path,
};
}
Future<String?> backupDatabase() async {
try {
final db = await database;
final directory = await getApplicationDocumentsDirectory();
final originalPath = db.path;
final backupDirectory = Directory(join(directory.path, 'backups'));
if (!await backupDirectory.exists()) {
await backupDirectory.create(recursive: true);
}
final backupPath = join(backupDirectory.path,
'train_database_backup_${DateTime.now().millisecondsSinceEpoch}.db');
await File(originalPath).copy(backupPath);
return backupPath;
} catch (e) {
return null;
}
}
Future<void> deleteRecords(List<String> uniqueIds) async {
final db = await database;
for (String id in uniqueIds) {
await db.delete(
'train_records',
where: 'uniqueId = ?',
whereArgs: [id],
);
}
}
Future<void> close() async {
if (_database != null) {
await _database!.close();
_database = null;
}
}
Future<String?> exportDataAsJson({String? customPath}) async {
try {
final records = await getAllRecords();
final exportData = {
'records': records.map((r) => r.toDatabaseJson()).toList(),
};
final jsonString = jsonEncode(exportData);
String filePath;
if (customPath != null) {
filePath = customPath;
} else {
final tempDir = Directory.systemTemp;
final fileName =
'LBJ_Console_${DateTime.now().year}${DateTime.now().month.toString().padLeft(2, '0')}${DateTime.now().day.toString().padLeft(2, '0')}.json';
filePath = join(tempDir.path, fileName);
}
await File(filePath).writeAsString(jsonString);
return filePath;
} catch (e) {
return null;
}
}
Future<bool> importDataFromJson(String filePath) async {
try {
final jsonString = await File(filePath).readAsString();
final importData = jsonDecode(jsonString);
final db = await database;
await db.transaction((txn) async {
await txn.delete(trainRecordsTable);
if (importData['records'] != null) {
final records =
List<Map<String, dynamic>>.from(importData['records']);
for (final record in records) {
await txn.insert(trainRecordsTable, record);
}
}
});
return true;
} catch (e) {
return false;
}
}
Future<bool> deleteExportFile(String filePath) async {
try {
final file = File(filePath);
if (await file.exists()) {
await file.delete();
return true;
}
return false;
} catch (e) {
return false;
}
}
}

View File

@@ -0,0 +1,17 @@
import 'package:lbjconsole/util/loco_type_util.dart';
class LocoTypeService {
static final LocoTypeService _instance = LocoTypeService._internal();
factory LocoTypeService() => _instance;
LocoTypeService._internal();
bool _isInitialized = false;
Future<void> initialize() async {
if (_isInitialized) return;
_isInitialized = true;
}
bool get isInitialized => _isInitialized;
}

View File

@@ -0,0 +1,85 @@
import 'package:lbjconsole/models/train_record.dart';
import 'package:lbjconsole/models/merged_record.dart';
class MergeService {
static String? _generateGroupKey(TrainRecord record, GroupBy groupBy) {
final train = record.train.trim();
final loco = record.loco.trim();
final hasTrain = train.isNotEmpty && train != "<NUL>";
final hasLoco = loco.isNotEmpty && loco != "<NUL>";
switch (groupBy) {
case GroupBy.trainOnly:
return hasTrain ? train : null;
case GroupBy.locoOnly:
return hasLoco ? loco : null;
case GroupBy.trainOrLoco:
if (hasTrain) return train;
if (hasLoco) return loco;
return null;
case GroupBy.trainAndLoco:
return (hasTrain && hasLoco) ? "${train}_$loco" : null;
}
}
static List<Object> getMixedList(
List<TrainRecord> allRecords, MergeSettings settings) {
if (!settings.enabled) {
allRecords
.sort((a, b) => b.receivedTimestamp.compareTo(a.receivedTimestamp));
return allRecords;
}
final now = DateTime.now();
final validRecords = settings.timeWindow.duration == null
? allRecords
: allRecords
.where((r) =>
now.difference(r.receivedTimestamp) <=
settings.timeWindow.duration!)
.toList();
final groupedRecords = <String, List<TrainRecord>>{};
for (final record in validRecords) {
final key = _generateGroupKey(record, settings.groupBy);
if (key != null) {
groupedRecords.putIfAbsent(key, () => []).add(record);
}
}
final List<MergedTrainRecord> mergedRecords = [];
final Set<String> mergedRecordIds = {};
groupedRecords.forEach((key, group) {
if (group.length >= 2) {
group
.sort((a, b) => b.receivedTimestamp.compareTo(a.receivedTimestamp));
final latestRecord = group.first;
mergedRecords.add(MergedTrainRecord(
groupKey: key,
records: group,
latestRecord: latestRecord,
));
for (final record in group) {
mergedRecordIds.add(record.uniqueId);
}
}
});
final singleRecords =
allRecords.where((r) => !mergedRecordIds.contains(r.uniqueId)).toList();
final List<Object> mixedList = [...mergedRecords, ...singleRecords];
mixedList.sort((a, b) {
final aTime = a is MergedTrainRecord
? a.latestRecord.receivedTimestamp
: (a as TrainRecord).receivedTimestamp;
final bTime = b is MergedTrainRecord
? b.latestRecord.receivedTimestamp
: (b as TrainRecord).receivedTimestamp;
return bTime.compareTo(aTime);
});
return mixedList;
}
}

View File

@@ -0,0 +1,135 @@
import 'dart:async';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:lbjconsole/models/train_record.dart';
class NotificationService {
static const String channelId = 'lbj_messages';
static const String channelName = 'LBJ Messages';
static const String channelDescription = 'Receive LBJ messages';
final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
int _notificationId = 1000;
bool _notificationsEnabled = true;
final StreamController<bool> _settingsController =
StreamController<bool>.broadcast();
Stream<bool> get settingsStream => _settingsController.stream;
Future<void> initialize() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
final InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
);
await _notificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (details) {},
);
await _createNotificationChannel();
_notificationsEnabled = await isNotificationEnabled();
_settingsController.add(_notificationsEnabled);
}
Future<void> _createNotificationChannel() async {
const AndroidNotificationChannel channel = AndroidNotificationChannel(
channelId,
channelName,
description: channelDescription,
importance: Importance.high,
enableVibration: true,
playSound: true,
);
await _notificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
}
Future<void> showTrainNotification(TrainRecord record) async {
if (!_notificationsEnabled) return;
if (!_isValidValue(record.train) ||
!_isValidValue(record.route) ||
!_isValidValue(record.directionText)) {
return;
}
final String title = '列车信息更新';
final String body = _buildNotificationContent(record);
final AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
channelId,
channelName,
channelDescription: channelDescription,
importance: Importance.high,
priority: Priority.high,
ticker: 'ticker',
styleInformation: BigTextStyleInformation(body),
);
final NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await _notificationsPlugin.show(
_notificationId++,
title,
body,
platformChannelSpecifics,
payload: 'train_${record.train}',
);
}
String _buildNotificationContent(TrainRecord record) {
final buffer = StringBuffer();
buffer.writeln('车次: ${record.fullTrainNumber}');
buffer.writeln('线路: ${record.route}');
buffer.writeln('方向: ${record.directionText}');
if (_isValidValue(record.speed)) {
buffer.writeln('速度: ${record.speed} km/h');
}
if (_isValidValue(record.positionInfo)) {
buffer.writeln('位置: ${record.positionInfo}');
}
buffer.writeln('时间: ${record.formattedTime}');
return buffer.toString().trim();
}
bool _isValidValue(String? value) {
if (value == null || value.isEmpty) return false;
final trimmed = value.trim();
return trimmed.isNotEmpty &&
trimmed != 'NUL' &&
trimmed != 'NA' &&
trimmed != '*';
}
Future<void> enableNotifications(bool enable) async {
_notificationsEnabled = enable;
_settingsController.add(_notificationsEnabled);
}
Future<bool> isNotificationEnabled() async {
return _notificationsEnabled;
}
Future<void> cancelAllNotifications() async {
await _notificationsPlugin.cancelAll();
}
void dispose() {
_settingsController.close();
}
}

175
lib/themes/app_theme.dart Normal file
View File

@@ -0,0 +1,175 @@
import 'package:flutter/material.dart';
class AppTheme {
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
scaffoldBackgroundColor: Colors.black,
canvasColor: Colors.black,
cardColor: const Color(0xFF121212),
primaryColor: Colors.blue,
colorScheme: ColorScheme.dark(
primary: Colors.blue,
secondary: Colors.blueAccent,
surface: const Color(0xFF121212),
background: Colors.black,
onSurface: Colors.white,
onBackground: Colors.white,
),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.black,
elevation: 0,
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
iconTheme: IconThemeData(color: Colors.white),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: Color(0xFF121212),
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
type: BottomNavigationBarType.fixed,
),
cardTheme: CardTheme(
color: const Color(0xFF1E1E1E),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
textTheme: const TextTheme(
headlineLarge: TextStyle(
color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold),
headlineMedium: TextStyle(
color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold),
headlineSmall: TextStyle(
color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
titleLarge: TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.w600),
titleMedium: TextStyle(
color: Colors.white, fontSize: 18, fontWeight: FontWeight.w500),
titleSmall: TextStyle(
color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500),
bodyLarge: TextStyle(color: Colors.white, fontSize: 16),
bodyMedium: TextStyle(color: Colors.white70, fontSize: 14),
bodySmall: TextStyle(color: Colors.white60, fontSize: 12),
labelLarge: TextStyle(
color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500),
labelMedium: TextStyle(color: Colors.white70, fontSize: 12),
labelSmall: TextStyle(color: Colors.white60, fontSize: 10),
),
iconTheme: const IconThemeData(color: Colors.white),
dividerTheme: const DividerThemeData(
color: Color(0xFF2A2A2A),
thickness: 1,
),
switchTheme: SwitchThemeData(
thumbColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return Colors.blue;
}
return Colors.grey;
}),
trackColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return Colors.blue.withOpacity(0.5);
}
return Colors.grey.withOpacity(0.5);
}),
),
dialogTheme: DialogTheme(
backgroundColor: const Color(0xFF1E1E1E),
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
titleTextStyle: const TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
contentTextStyle: const TextStyle(color: Colors.white70, fontSize: 16),
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
);
}
static const Color primaryBlack = Colors.black;
static const Color secondaryBlack = Color(0xFF121212);
static const Color tertiaryBlack = Color(0xFF1E1E1E);
static const Color dividerColor = Color(0xFF2A2A2A);
static const Color textPrimary = Colors.white;
static const Color textSecondary = Color(0xFFB3B3B3);
static const Color textTertiary = Color(0xFF808080);
static const Color accentBlue = Colors.blue;
static const Color accentBlueLight = Color(0xFF64B5F6);
static const Color errorRed = Color(0xFFCF6679);
static const Color successGreen = Color(0xFF4CAF50);
static const Color warningOrange = Color(0xFFFF9800);
static const TextStyle titleLarge = TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: textPrimary,
);
static const TextStyle titleMedium = TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: textPrimary,
);
static const TextStyle bodyLarge = TextStyle(
fontSize: 16,
color: textPrimary,
);
static const TextStyle bodyMedium = TextStyle(
fontSize: 14,
color: textSecondary,
);
static const TextStyle caption = TextStyle(
fontSize: 12,
color: textTertiary,
);
static const TextStyle labelLarge = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: textPrimary,
);
static const double spacingXS = 4.0;
static const double spacingS = 8.0;
static const double spacingM = 16.0;
static const double spacingL = 24.0;
static const double spacingXL = 32.0;
static const double spacingXXL = 48.0;
static const double radiusS = 4.0;
static const double radiusM = 8.0;
static const double radiusL = 12.0;
static const double radiusXL = 16.0;
static List<BoxShadow> get cardShadow => [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
];
static List<BoxShadow> get buttonShadow => [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
),
];
}

View File

@@ -0,0 +1,150 @@
import 'dart:convert';
import 'package:flutter/services.dart';
class LocoInfoUtil {
static final List<LocoInfo> _locoData = [];
static bool _initialized = false;
static Future<void> initialize() async {
if (_initialized) return;
try {
final csvData = await rootBundle.loadString('assets/loco_info.csv');
final lines = csvData.split('\n');
for (final line in lines) {
if (line.trim().isEmpty) continue;
final fields = _parseCsvLine(line);
if (fields.length >= 4) {
try {
final model = fields[0];
final start = int.parse(fields[1]);
final end = int.parse(fields[2]);
final owner = fields[3];
final alias = fields.length > 4 ? fields[4] : '';
final manufacturer = fields.length > 5 ? fields[5] : '';
_locoData.add(LocoInfo(
model: model,
start: start,
end: end,
owner: owner,
alias: alias,
manufacturer: manufacturer,
));
} catch (e) {}
}
}
_initialized = true;
} catch (e) {
_initialized = true;
}
}
static List<String> _parseCsvLine(String line) {
final fields = <String>[];
final buffer = StringBuffer();
bool inQuotes = false;
for (int i = 0; i < line.length; i++) {
final char = line[i];
if (char == '"') {
inQuotes = !inQuotes;
} else if (char == ',' && !inQuotes) {
fields.add(buffer.toString().trim());
buffer.clear();
} else {
buffer.write(char);
}
}
fields.add(buffer.toString().trim());
return fields;
}
static LocoInfo? findLocoInfo(String model, String number) {
if (!_initialized || model.isEmpty || number.isEmpty) {
return null;
}
try {
final cleanNumber = number.trim().replaceAll('-', '').replaceAll(' ', '');
final num = cleanNumber.length > 4
? int.parse(cleanNumber.substring(cleanNumber.length - 4))
: int.parse(cleanNumber);
for (final info in _locoData) {
if (info.model == model && num >= info.start && num <= info.end) {
return info;
}
}
} catch (e) {
return null;
}
return null;
}
static String? getLocoInfoDisplay(String model, String number) {
if (_locoData.isEmpty) return null;
final modelTrimmed = model.trim();
final numberTrimmed = number.trim();
if (modelTrimmed.isEmpty ||
numberTrimmed.isEmpty ||
numberTrimmed == "<NUL>") {
return null;
}
final cleanNumber = numberTrimmed.replaceAll('-', '').replaceAll(' ', '');
final numberSuffix = cleanNumber.length >= 4
? cleanNumber.substring(cleanNumber.length - 4)
: cleanNumber.padLeft(4, '0');
final numberInt = int.tryParse(numberSuffix);
if (numberInt == null) {
return null;
}
for (final info in _locoData) {
if (info.model == modelTrimmed &&
numberInt >= info.start &&
numberInt <= info.end) {
final buffer = StringBuffer();
buffer.write(info.owner);
if (info.alias.isNotEmpty) {
buffer.write(' - ${info.alias}');
}
if (info.manufacturer.isNotEmpty) {
buffer.write(' - ${info.manufacturer}');
}
return buffer.toString();
}
}
return null;
}
}
class LocoInfo {
final String model;
final int start;
final int end;
final String owner;
final String alias;
final String manufacturer;
LocoInfo({
required this.model,
required this.start,
required this.end,
required this.owner,
required this.alias,
required this.manufacturer,
});
}

View File

@@ -0,0 +1,54 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/services.dart';
class LocoTypeUtil {
static final LocoTypeUtil _instance = LocoTypeUtil._internal();
factory LocoTypeUtil() => _instance;
LocoTypeUtil._internal() {
_syncInitialize();
}
final Map<String, String> _locoTypeMap = {};
bool _isInitialized = false;
void _syncInitialize() {
try {
rootBundle.loadString('assets/loco_type_info.csv').then((csvData) {
final lines = const LineSplitter().convert(csvData);
for (final line in lines) {
final trimmedLine = line.trim();
if (trimmedLine.isEmpty) continue;
final parts = trimmedLine.split(',');
if (parts.length >= 2) {
final code = parts[0].trim();
final type = parts[1].trim();
_locoTypeMap[code] = type;
}
}
_isInitialized = true;
});
} catch (e) {}
}
@deprecated
Future<void> initialize() async {}
String? getLocoTypeByCode(String code) {
return _locoTypeMap[code];
}
String? getLocoTypeByLocoNumber(String locoNumber) {
if (locoNumber.length < 3) return null;
final prefix = locoNumber.substring(0, 3);
return getLocoTypeByCode(prefix);
}
Map<String, String> getAllMappings() {
return Map.from(_locoTypeMap);
}
bool get isInitialized => _isInitialized;
int get mappingCount => _locoTypeMap.length;
}

View File

@@ -0,0 +1,68 @@
import 'dart:convert';
import 'package:flutter/services.dart';
class TrainTypeUtil {
static final List<_TrainTypePattern> _patterns = [];
static bool _initialized = false;
static Future<void> initialize() async {
if (_initialized) return;
try {
final csvData =
await rootBundle.loadString('assets/train_number_info.csv');
final lines = csvData.split('\n');
for (final line in lines) {
if (line.trim().isEmpty) continue;
final firstQuoteEnd = line.indexOf('"', 1);
if (firstQuoteEnd > 0 && firstQuoteEnd < line.length - 1) {
final regex = line.substring(1, firstQuoteEnd);
final remainingPart = line.substring(firstQuoteEnd + 1).trim();
if (remainingPart.startsWith(',"') && remainingPart.endsWith('"')) {
final type = remainingPart.substring(2, remainingPart.length - 1);
try {
_patterns.add(_TrainTypePattern(RegExp(regex), type));
} catch (e) {}
}
}
}
_initialized = true;
} catch (e) {
_initialized = true;
}
}
static String? getTrainType(String lbjClass, String train) {
if (!_initialized) {
return null;
}
final lbjClassTrimmed = lbjClass.trim();
final trainTrimmed = train.trim();
if (trainTrimmed.isEmpty || trainTrimmed == "<NUL>") {
return null;
}
final actualTrain =
lbjClassTrimmed == "NA" ? trainTrimmed : lbjClassTrimmed + trainTrimmed;
for (final pattern in _patterns) {
if (pattern.regex.hasMatch(actualTrain)) {
return pattern.type;
}
}
return null;
}
}
class _TrainTypePattern {
final RegExp regex;
final String type;
_TrainTypePattern(this.regex, this.type);
}

1
linux/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
flutter/ephemeral

145
linux/CMakeLists.txt Normal file
View File

@@ -0,0 +1,145 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "lbjconsole")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "org.noxylva.lbjconsole")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View File

@@ -0,0 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View File

@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

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

View File

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

6
linux/main.cc Normal file
View File

@@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

124
linux/my_application.cc Normal file
View File

@@ -0,0 +1,124 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "lbjconsole");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "lbjconsole");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}

18
linux/my_application.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

7
macos/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/

View File

@@ -0,0 +1 @@
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@@ -0,0 +1,30 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import file_picker
import flutter_blue_plus_darwin
import flutter_local_notifications
import geolocator_apple
import package_info_plus
import path_provider_foundation
import share_plus
import shared_preferences_foundation
import sqflite_darwin
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@@ -0,0 +1,705 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
buildPhases = (
33CC111E2044C6BF0003C045 /* ShellScript */,
);
dependencies = (
);
name = "Flutter Assemble";
productName = FLX;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
remoteInfo = Runner;
};
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
33CC110E2044A8840003C045 /* Bundle Framework */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* lbjconsole.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "lbjconsole.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* lbjconsole.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* lbjconsole.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 33CC10EC2044A3C60003C045;
};
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
33CC111A2044C6BA0003C045 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
331C80D4294CF70F00263BE5 /* RunnerTests */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C80D3294CF70F00263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C80D1294CF70F00263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC10EC2044A3C60003C045 /* Runner */;
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
};
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
33CC10F52044A3C60003C045 /* Base */,
);
name = MainMenu.xib;
path = Runner;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lbjconsole.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lbjconsole";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lbjconsole.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lbjconsole";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.noxylva.lbjconsole.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lbjconsole.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lbjconsole";
};
name = Profile;
};
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Profile;
};
338D0CEA231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Profile;
};
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
};
33CC10F92044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
33CC10FA2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
33CC10FC2044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
33CC10FD2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
33CC111D2044C6BA0003C045 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C80DB294CF71000263BE5 /* Debug */,
331C80DC294CF71000263BE5 /* Release */,
331C80DD294CF71000263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10F92044A3C60003C045 /* Debug */,
33CC10FA2044A3C60003C045 /* Release */,
338D0CE9231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10FC2044A3C60003C045 /* Debug */,
33CC10FD2044A3C60003C045 /* Release */,
338D0CEA231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC111C2044C6BA0003C045 /* Debug */,
33CC111D2044C6BA0003C045 /* Release */,
338D0CEB231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}

View File

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

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lbjconsole.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lbjconsole.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lbjconsole.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lbjconsole.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

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