diff --git a/.github/workflows/flutter_build.yml b/.github/workflows/flutter_build.yml new file mode 100644 index 0000000..7a6c1ee --- /dev/null +++ b/.github/workflows/flutter_build.yml @@ -0,0 +1,61 @@ +name: Flutter Build + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.35.2' + channel: 'stable' + + - name: Update compileSdkVersion + run: | + sed -i 's/compileSdk = 35/compileSdk = 36/g' android/app/build.gradle + + - name: Get dependencies + run: flutter pub get + + - name: Create keystore + run: | + echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.jks + + - name: Build APK + run: flutter build apk --release + env: + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + + - name: Build App Bundle + run: flutter build appbundle --release + env: + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + + - name: Rename APK with tag + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + cp build/app/outputs/flutter-apk/app-release.apk LBJ_Console_${TAG_NAME}_android_release.apk + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: | + LBJ_Console_${{ github.ref_name }}_android_release.apk + build/app/outputs/bundle/release/app-release.aab + name: ${{ github.ref_name }} + draft: false + prerelease: false \ No newline at end of file diff --git a/README.md b/README.md index 3359a62..c0329c1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,23 @@ # LBJ_Console -这是 LBJ_Console 的 Flutter 实现。 \ No newline at end of file +LBJ Console 是一款应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括: + +- 接收列车预警消息,支持可选的手机推送通知。 +- 在地图上显示预警消息的 GPS 信息。 +- 基于内置数据文件显示机车配属,机车类型和车次类型。 + +[android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。 + + +## 数据文件 + +LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机车配属和车次信息的展示: +- `loco_info.csv`:包含机车配属信息,格式为 `机车型号,机车编号起始值,机车编号结束值,所属铁路局及机务段,备注`。 +- `loco_type_info.csv`:包含机车类型编码信息,格式为 `机车类型编码前缀,机车类型`。 +- `train_info.csv`:包含车次类型信息,格式为 `正则表达式,车次类型`。 + +数据来源于网络,可能存在错误或不完整,欢迎通过提交 Pull Request 共同完善数据准确性。 + +# 许可证 + +该项目采用 GNU 通用公共许可证 v3.0(GPLv3)授权。 diff --git a/android/app/build.gradle b/android/app/build.gradle index bc5f8c5..c5b46c3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -25,8 +25,8 @@ if (flutterVersionName == null) { android { namespace = "org.noxylva.lbjconsole.flutter" - compileSdk = 35 - ndkVersion = "25.1.8937393" + compileSdk = 36 + ndkVersion = "26.1.10909125" compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -49,9 +49,9 @@ android { signingConfigs { release { storeFile file("../../keystore.jks") - storePassword System.getenv("KEYSTORE_PASSWORD") ?: "android" - keyAlias System.getenv("KEY_ALIAS") ?: "androidkey" - keyPassword System.getenv("KEY_PASSWORD") ?: "android" + storePassword System.getenv("KEYSTORE_PASSWORD") + keyAlias System.getenv("KEY_ALIAS") + keyPassword System.getenv("KEY_PASSWORD") } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 5e6b542..3c85cfe 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ 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 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index e07c1cb..4f52071 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { 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 + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false } include ":app" diff --git a/assets/loco_type_info.csv b/assets/loco_type_info.csv index 347e896..c6433a2 100644 --- a/assets/loco_type_info.csv +++ b/assets/loco_type_info.csv @@ -125,11 +125,16 @@ 305,CRH5 306,CRH380A 307,CRH380B +3073,CRH380BL +3075,CRH380BG 308,CRH380C 309,CRH380D 310,CRH6A 311,CR400AF +3111,CR400AF-Z +3112,CR400AF-S 312,CR400BF +3125,CR400BF-G 313,CR300AF 314,CR300BF 315,CRH2E @@ -139,4 +144,5 @@ 332,CJ3 333,CJ4 334,CJ5 -335,CJ6 \ No newline at end of file +335,CJ6 +400,GCD-1000J \ No newline at end of file diff --git a/assets/train_number_info.csv b/assets/train_number_info.csv index 9213b85..2b71c0d 100644 --- a/assets/train_number_info.csv +++ b/assets/train_number_info.csv @@ -23,8 +23,8 @@ "^[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})$","跨两局图定普通旅客快车" +"^[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)$","管内图定普通旅客快车" diff --git a/lib/screens/history_screen.dart b/lib/screens/history_screen.dart index a9eee7f..3454cca 100644 --- a/lib/screens/history_screen.dart +++ b/lib/screens/history_screen.dart @@ -244,12 +244,12 @@ class HistoryScreenState extends State { switch (groupBy) { case GroupBy.trainOnly: - return loco != latestLoco && loco.isNotEmpty ? "机车: $loco" : ""; + return loco != latestLoco && loco.isNotEmpty ? loco : ""; case GroupBy.locoOnly: - return train != latestTrain && train.isNotEmpty ? "车次: $train" : ""; + return train != latestTrain && train.isNotEmpty ? train : ""; case GroupBy.trainOrLoco: - if (train.isNotEmpty && train != latestTrain) return "车次: $train"; - if (loco.isNotEmpty && loco != latestLoco) return "机车: $loco"; + if (train.isNotEmpty && train != latestTrain) return train; + if (loco.isNotEmpty && loco != latestLoco) return loco; return ""; case GroupBy.trainAndLoco: return ""; @@ -261,8 +261,11 @@ class HistoryScreenState extends State { if (record.route.isNotEmpty && record.route != "") parts.add(record.route); if (record.direction != 0) parts.add(record.direction == 1 ? "下" : "上"); - if (record.position.isNotEmpty && record.position != "") - parts.add("${record.position}K"); + if (record.position.isNotEmpty && record.position != "") { + final position = record.position; + final cleanPosition = position.endsWith('.') ? position.substring(0, position.length - 1) : position; + parts.add("${cleanPosition}K"); + } return parts.join(' '); } @@ -385,12 +388,12 @@ class HistoryScreenState extends State { (record.time == "" || record.time.isEmpty) ? record.receivedTimestamp.toString().split(".")[0] : record.time.split("\n")[0], - style: const TextStyle(fontSize: 12, color: Colors.grey), + style: const TextStyle(fontSize: 11, color: Colors.grey), overflow: TextOverflow.ellipsis)), if (trainType.isNotEmpty) Flexible( child: Text(trainType, - style: const TextStyle(fontSize: 12, color: Colors.grey), + style: const TextStyle(fontSize: 11, color: Colors.grey), overflow: TextOverflow.ellipsis)) ]), const SizedBox(height: 2), @@ -478,13 +481,13 @@ class HistoryScreenState extends State { if (isValidRoute && isValidPosition) const SizedBox(width: 4), if (isValidPosition) Flexible( - child: Text("$position K", + child: Text("${position.trim().endsWith('.') ? position.trim().substring(0, position.trim().length - 1) : position.trim()}K", style: const TextStyle(fontSize: 16, color: Colors.white), overflow: TextOverflow.ellipsis)) ])), if (isValidSpeed) - Text("$speed km/h", + Text("${speed.replaceAll(' ', '')} km/h", style: const TextStyle(fontSize: 16, color: Colors.white), textAlign: TextAlign.right) ])); diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 7c2110f..89dec73 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -401,10 +401,10 @@ class _MapScreenState extends State { _buildMaterial3DetailRow( context, "类型", record.trainType), _buildMaterial3DetailRow( - context, "速度", "${record.speed} km/h"), + context, "速度", "${record.speed.replaceAll(' ', '')} km/h"), _buildMaterial3DetailRow( - context, "位置", record.position), - _buildMaterial3DetailRow(context, "路线", record.route), + context, "位置", record.position.trim().endsWith('.') ? '${record.position.trim().substring(0, record.position.trim().length - 1)}K' : '${record.position.trim()}K'), + _buildMaterial3DetailRow(context, "路线", record.route.trim().endsWith('.') ? record.route.trim().substring(0, record.route.trim().length - 1) : record.route.trim()), _buildMaterial3DetailRow( context, "机车", "${record.locoType}-${record.loco}"), _buildMaterial3DetailRow(context, "坐标", diff --git a/lib/themes/app_theme.dart b/lib/themes/app_theme.dart index cfcd3b1..3c09907 100644 --- a/lib/themes/app_theme.dart +++ b/lib/themes/app_theme.dart @@ -33,7 +33,7 @@ class AppTheme { unselectedItemColor: Colors.grey, type: BottomNavigationBarType.fixed, ), - cardTheme: CardTheme( + cardTheme: CardThemeData( color: const Color(0xFF1E1E1E), elevation: 2, shape: RoundedRectangleBorder( @@ -82,7 +82,7 @@ class AppTheme { return Colors.grey.withOpacity(0.5); }), ), - dialogTheme: DialogTheme( + dialogTheme: DialogThemeData( backgroundColor: const Color(0xFF1E1E1E), elevation: 8, shape: RoundedRectangleBorder( diff --git a/lib/util/loco_type_util.dart b/lib/util/loco_type_util.dart index 014fa3f..3dd84d5 100644 --- a/lib/util/loco_type_util.dart +++ b/lib/util/loco_type_util.dart @@ -35,11 +35,27 @@ class LocoTypeUtil { Future initialize() async {} String? getLocoTypeByCode(String code) { - return _locoTypeMap[code]; + if (_locoTypeMap.containsKey(code)) { + return _locoTypeMap[code]; + } + + if (code.length >= 4) { + final prefix3 = code.substring(0, 3); + return _locoTypeMap[prefix3]; + } + + return null; } String? getLocoTypeByLocoNumber(String locoNumber) { if (locoNumber.length < 3) return null; + + if (locoNumber.length >= 4) { + final longCode = locoNumber.substring(0, 4); + final result = getLocoTypeByCode(longCode); + if (result != null) return result; + } + final prefix = locoNumber.substring(0, 3); return getLocoTypeByCode(prefix); } diff --git a/pubspec.lock b/pubspec.lock index a1cc094..6dc4aeb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.flutter-io.cn" source: hosted - version: "72.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.2" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.flutter-io.cn" source: hosted - version: "6.7.0" + version: "6.4.1" archive: dependency: transitive description: @@ -130,10 +125,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -154,10 +149,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -170,10 +165,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.flutter-io.cn" source: hosted - version: "1.18.0" + version: "1.19.1" console: dependency: transitive description: @@ -226,10 +221,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.flutter-io.cn" source: hosted - version: "2.3.7" + version: "2.3.6" dbus: dependency: transitive description: @@ -242,10 +237,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: @@ -569,26 +564,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.flutter-io.cn" source: hosted - version: "10.0.5" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.5" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -621,22 +616,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.1.2-main.4" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -649,10 +636,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.flutter-io.cn" source: hosted - version: "1.15.0" + version: "1.16.0" mgrs_dart: dependency: transitive description: @@ -713,10 +700,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.flutter-io.cn" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider: dependency: "direct main" description: @@ -993,7 +980,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: @@ -1070,18 +1057,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.flutter-io.cn" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -1118,10 +1105,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.2" + version: "0.7.6" timezone: dependency: transitive description: @@ -1230,10 +1217,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -1315,5 +1302,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.4 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 54802b1..89e4be3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.1.4-flutter +version: 0.1.5-flutter environment: sdk: ^3.5.4