7 Commits

Author SHA1 Message Date
Nedifinita
7772112658 chore: upgrade AGP and Gradle 2025-11-23 22:30:21 +08:00
Nedifinita
5a232eaa6c Merge branch 'flutter' of https://github.com/undef-i/LBJ_Console into flutter 2025-11-23 21:44:24 +08:00
Nedifinita
a47c6a5745 refactor 2025-11-23 21:43:47 +08:00
undef-i
777efdda45 Merge pull request #3 from zrj24/flutter
add loco_type DF11Z,GX-160
2025-11-23 21:20:39 +08:00
Nedifinita
fe2769f479 chore 2025-11-23 21:18:54 +08:00
zrj24
0dc256b5d7 add loco_type DF11Z,GX-160 2025-11-23 21:06:31 +08:00
Nedifinita
8615e53c85 docs: update README.md 2025-11-01 22:41:59 +08:00
25 changed files with 115 additions and 282 deletions

View File

@@ -38,12 +38,6 @@ jobs:
KEY_ALIAS: ${{ secrets.KEY_ALIAS }} KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} 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 - name: Rename APK with tag
run: | run: |
@@ -55,7 +49,6 @@ jobs:
with: with:
files: | files: |
LBJ_Console_${{ github.ref_name }}_android_release.apk LBJ_Console_${{ github.ref_name }}_android_release.apk
build/app/outputs/bundle/release/app-release.aab
name: ${{ github.ref_name }} name: ${{ github.ref_name }}
draft: false draft: false
prerelease: false prerelease: false

View File

@@ -1,15 +1,17 @@
# LBJ_Console # LBJ_Console
LBJ Console 是一应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括: LBJ Console 是一应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
- 接收列车预警消息,支持可选的手机推送通知。 - 接收列车预警消息,支持可选的手机推送通知。
- 监控指定列车的轨迹,在地图上显示。 - 监控指定列车的轨迹,在地图上显示。
- 在地图上显示预警消息的 GPS 信息。 - 在地图上显示预警消息的 GPS 信息。
- 基于内置数据文件显示机车配属,机车类型和车次类型。 - 基于内置数据文件显示机车配属,机车类型和车次类型。
- [WIP] 从 RTL-TCP 获取数据 - 连接 RTL-TCP 服务器获取预警消息
[android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。 [android](https://github.com/undef-i/LBJ_Console/tree/android) 分支包含项目早期基于 Android 平台的实现代码,已实现基本功能,现已停止开发。
本项目为个人业余项目,代码质量和实现细节可能不尽如人意,敬请见谅。
## 数据文件 ## 数据文件
LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机车配属和车次信息的展示: LBJ Console 依赖以下数据文件,位于 `assets` 目录,用于支持机车配属和车次信息的展示:

View File

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

View File

@@ -18,7 +18,7 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.6.0" apply false id "com.android.application" version "8.13.0" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false id "org.jetbrains.kotlin.android" version "2.1.0" apply false
} }

View File

@@ -57,6 +57,7 @@
151,NJ2 151,NJ2
152,东风7G 152,东风7G
153,NDJ3 153,NDJ3
156,东风11Z
157,FXN3D 157,FXN3D
158,东风11G 158,东风11G
160,HXN3 160,HXN3
@@ -146,3 +147,4 @@
334,CJ5 334,CJ5
335,CJ6 335,CJ6
400,GCD-1000J 400,GCD-1000J
403,GX-160
1 001 解放
57 151 NJ2
58 152 东风7G
59 153 NDJ3
60 156 东风11Z
61 157 FXN3D
62 158 东风11G
63 160 HXN3
147 334 CJ5
148 335 CJ6
149 400 GCD-1000J
150 403 GX-160

View File

@@ -3,9 +3,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:lbjconsole/screens/main_screen.dart'; import 'package:lbjconsole/screens/main_screen.dart';
import 'package:lbjconsole/util/train_type_util.dart'; import 'package:lbjconsole/util/train_type_util.dart';
import 'package:lbjconsole/util/loco_info_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/loco_type_service.dart';
import 'package:lbjconsole/services/database_service.dart';
import 'package:lbjconsole/services/background_service.dart'; import 'package:lbjconsole/services/background_service.dart';
void main() async { void main() async {

View File

@@ -45,7 +45,7 @@ class MapState {
@override @override
String toString() { String toString() {
return 'MapState(zoom: ' + zoom.toString() + ', centerLat: ' + centerLat.toString() + ', centerLng: ' + centerLng.toString() + ', bearing: ' + bearing.toString() + ')'; return 'MapState(zoom: $zoom, centerLat: $centerLat, centerLng: $centerLng, bearing: $bearing)';
} }
@override @override

View File

@@ -340,22 +340,6 @@ class HistoryScreenState extends State<HistoryScreen> {
} catch (e) {} } catch (e) {}
} }
String _getGroupKeyForRecord(TrainRecord record, MergeSettings settings) {
switch (settings.groupBy) {
case GroupBy.trainOnly:
return record.train.trim();
case GroupBy.locoOnly:
return record.loco.trim();
case GroupBy.trainAndLoco:
return '${record.train.trim()}-${record.loco.trim()}';
case GroupBy.trainOrLoco:
final train = record.train.trim();
if (train.isNotEmpty) return train;
final loco = record.loco.trim();
if (loco.isNotEmpty) return loco;
return '';
}
}
bool _hasDataChanged(List<Object> newItems) { bool _hasDataChanged(List<Object> newItems) {
if (_displayItems.length != newItems.length) return true; if (_displayItems.length != newItems.length) return true;

View File

@@ -3,8 +3,6 @@ import 'package:flutter/services.dart';
import 'dart:async'; import 'dart:async';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; 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/history_screen.dart';
import 'package:lbjconsole/screens/map_screen.dart'; import 'package:lbjconsole/screens/map_screen.dart';
import 'package:lbjconsole/screens/map_webview_screen.dart'; import 'package:lbjconsole/screens/map_webview_screen.dart';
@@ -307,17 +305,17 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
developer.log('rtl_tcp: setup_listener'); developer.log('rtl_tcp: setup_listener');
_settingsSubscription = _settingsSubscription =
DatabaseService.instance.onSettingsChanged((settings) { DatabaseService.instance.onSettingsChanged((settings) {
developer.log('rtl_tcp: settings_changed: enabled=${(settings?['rtlTcpEnabled'] ?? 0) == 1}, host=${settings?['rtlTcpHost']?.toString() ?? '127.0.0.1'}, port=${settings?['rtlTcpPort']?.toString() ?? '14423'}'); developer.log('rtl_tcp: settings_changed: enabled=${(settings['rtlTcpEnabled'] ?? 0) == 1}, host=${settings['rtlTcpHost']?.toString() ?? '127.0.0.1'}, port=${settings['rtlTcpPort']?.toString() ?? '14423'}');
if (mounted) { if (mounted) {
final rtlTcpEnabled = (settings?['rtlTcpEnabled'] ?? 0) == 1; final rtlTcpEnabled = (settings['rtlTcpEnabled'] ?? 0) == 1;
if (rtlTcpEnabled != _rtlTcpEnabled) { if (rtlTcpEnabled != _rtlTcpEnabled) {
setState(() { setState(() {
_rtlTcpEnabled = rtlTcpEnabled; _rtlTcpEnabled = rtlTcpEnabled;
}); });
if (rtlTcpEnabled) { if (rtlTcpEnabled) {
final host = settings?['rtlTcpHost']?.toString() ?? '127.0.0.1'; final host = settings['rtlTcpHost']?.toString() ?? '127.0.0.1';
final port = settings?['rtlTcpPort']?.toString() ?? '14423'; final port = settings['rtlTcpPort']?.toString() ?? '14423';
_connectToRtlTcp(host, port); _connectToRtlTcp(host, port);
} else { } else {
_rtlTcpConnectionSubscription?.cancel(); _rtlTcpConnectionSubscription?.cancel();
@@ -661,11 +659,12 @@ class _PixelPerfectBluetoothDialogState
Future<void> _startScan() async { Future<void> _startScan() async {
if (_scanState == _ScanState.scanning) return; if (_scanState == _ScanState.scanning) return;
if (mounted) if (mounted) {
setState(() { setState(() {
_devices.clear(); _devices.clear();
_scanState = _ScanState.scanning; _scanState = _ScanState.scanning;
}); });
}
await widget.bleService.startScan( await widget.bleService.startScan(
timeout: const Duration(seconds: 8), timeout: const Duration(seconds: 8),
onScanResults: (devices) { onScanResults: (devices) {
@@ -700,7 +699,7 @@ class _PixelPerfectBluetoothDialogState
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isConnected = widget.bleService.isConnected; final isConnected = widget.bleService.isConnected;
return AlertDialog( return AlertDialog(
title: Text(widget.rtlTcpEnabled ? 'RTL-TCP 模式' : '蓝牙设备'), title: Text(widget.rtlTcpEnabled ? 'RTL-TCP 服务器' : '蓝牙设备'),
content: SizedBox( content: SizedBox(
width: double.maxFinite, width: double.maxFinite,
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -784,7 +783,7 @@ class _PixelPerfectBluetoothDialogState
.titleMedium .titleMedium
?.copyWith(fontWeight: FontWeight.bold)), ?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('$currentAddress', Text(currentAddress,
style: TextStyle(color: isConnected ? Colors.green : Colors.grey)), style: TextStyle(color: isConnected ? Colors.green : Colors.grey)),
const SizedBox(height: 16), const SizedBox(height: 16),
if (_lastReceivedTime != null && isConnected) ...[ if (_lastReceivedTime != null && isConnected) ...[

View File

@@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' show sin, cos, sqrt, atan2, pi; import 'dart:math' show sin, cos, sqrt, atan2;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
@@ -26,19 +26,19 @@ class _MapScreenState extends State<MapScreen> {
double _currentRotation = 0.0; double _currentRotation = 0.0;
bool _isMapInitialized = false; bool _isMapInitialized = false;
bool _isFollowingLocation = false; final bool _isFollowingLocation = false;
bool _isLocationPermissionGranted = false; bool _isLocationPermissionGranted = false;
Timer? _locationTimer; Timer? _locationTimer;
String _selectedTimeFilter = 'unlimited'; String _selectedTimeFilter = 'unlimited';
final Map<String, Duration> _timeFilterOptions = { final Map<String, Duration> _timeFilterOptions = {
'unlimited': Duration.zero, 'unlimited': Duration.zero,
'1hour': Duration(hours: 1), '1hour': const Duration(hours: 1),
'6hours': Duration(hours: 6), '6hours': const Duration(hours: 6),
'12hours': Duration(hours: 12), '12hours': const Duration(hours: 12),
'24hours': Duration(hours: 24), '24hours': const Duration(hours: 24),
'7days': Duration(days: 7), '7days': const Duration(days: 7),
'30days': Duration(days: 30), '30days': const Duration(days: 30),
}; };
@override @override
@@ -81,8 +81,8 @@ class _MapScreenState extends State<MapScreen> {
if (lat == 39.9042 && lon == 116.4074) { if (lat == 39.9042 && lon == 116.4074) {
} else if (lat == 0.0 && lon == 0.0) { } else if (lat == 0.0 && lon == 0.0) {
} else { } else {
final beijingLat = 39.9042; const beijingLat = 39.9042;
final beijingLon = 116.4074; const beijingLon = 116.4074;
final distance = final distance =
_calculateDistance(lat, lon, beijingLat, beijingLon); _calculateDistance(lat, lon, beijingLat, beijingLon);
@@ -411,7 +411,7 @@ class _MapScreenState extends State<MapScreen> {
padding: padding:
const EdgeInsets.symmetric(horizontal: 6, vertical: 2), const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withOpacity(0.8), color: Colors.black.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(3), borderRadius: BorderRadius.circular(3),
), ),
child: Text( child: Text(
@@ -572,8 +572,8 @@ class _MapScreenState extends State<MapScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.surfaceVariant .surfaceContainerHighest
.withOpacity(0.3), .withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: Padding( child: Padding(

View File

@@ -32,7 +32,7 @@ class MapWebViewScreenState extends State<MapWebViewScreen>
double _currentRotation = 0.0; double _currentRotation = 0.0;
LatLng? _currentLocation; LatLng? _currentLocation;
LatLng? _lastTrainLocation; LatLng? _lastTrainLocation;
bool _isDataLoaded = false; final bool _isDataLoaded = false;
final Completer<void> _webViewReadyCompleter = Completer<void>(); final Completer<void> _webViewReadyCompleter = Completer<void>();
@override @override
@@ -299,7 +299,7 @@ class MapWebViewScreenState extends State<MapWebViewScreen>
_updateTrainMarkers(); _updateTrainMarkers();
} }
}); });
} catch (e, stackTrace) { } catch (e) {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });

View File

@@ -30,7 +30,7 @@ class RealtimeScreenState extends State<RealtimeScreen> {
List<LatLng> _selectedGroupRoute = []; List<LatLng> _selectedGroupRoute = [];
List<Marker> _mapMarkers = []; List<Marker> _mapMarkers = [];
bool _showMap = true; bool _showMap = true;
Set<String> _selectedGroupKeys = {}; final Set<String> _selectedGroupKeys = {};
LatLng? _userLocation; LatLng? _userLocation;
bool _isLocationPermissionGranted = false; bool _isLocationPermissionGranted = false;
Timer? _locationTimer; Timer? _locationTimer;
@@ -376,8 +376,7 @@ class RealtimeScreenState extends State<RealtimeScreen> {
} }
LatLng? _parsePositionFromRecord(TrainRecord record) { LatLng? _parsePositionFromRecord(TrainRecord record) {
if (record.positionInfo == null || if (record.positionInfo.isEmpty ||
record.positionInfo.isEmpty ||
record.positionInfo == '<NUL>') { record.positionInfo == '<NUL>') {
return null; return null;
} }
@@ -916,8 +915,8 @@ class RealtimeScreenState extends State<RealtimeScreen> {
flex: 1, flex: 1,
child: FlutterMap( child: FlutterMap(
mapController: _mapController, mapController: _mapController,
options: MapOptions( options: const MapOptions(
initialCenter: const LatLng(35.8617, 104.1954), initialCenter: LatLng(35.8617, 104.1954),
initialZoom: 2.0, initialZoom: 2.0,
), ),
children: [ children: [

View File

@@ -4,17 +4,13 @@ import 'dart:io';
import 'package:lbjconsole/models/merged_record.dart'; import 'package:lbjconsole/models/merged_record.dart';
import 'package:lbjconsole/services/database_service.dart'; import 'package:lbjconsole/services/database_service.dart';
import 'package:lbjconsole/services/ble_service.dart';
import 'package:lbjconsole/services/background_service.dart'; import 'package:lbjconsole/services/background_service.dart';
import 'package:lbjconsole/themes/app_theme.dart'; import 'package:lbjconsole/themes/app_theme.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:file_picker/file_picker.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'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:cross_file/cross_file.dart';
class SettingsScreen extends StatefulWidget { class SettingsScreen extends StatefulWidget {
final VoidCallback? onSettingsChanged; final VoidCallback? onSettingsChanged;
@@ -73,17 +69,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
Icon(Icons.wifi, Icon(Icons.wifi,
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12), const SizedBox(width: 12),
Text('RTL-TCP 接收', style: AppTheme.titleMedium), const Text('RTL-TCP ', style: AppTheme.titleMedium),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('启用RTL-TCP接收', style: AppTheme.bodyLarge), Text('启用 RTL-TCP', style: AppTheme.bodyLarge),
], ],
), ),
Switch( Switch(
@@ -94,7 +90,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
}); });
_saveSettings(); _saveSettings();
}, },
activeColor: Theme.of(context).colorScheme.primary, activeThumbColor: Theme.of(context).colorScheme.primary,
), ),
], ],
), ),
@@ -273,14 +269,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
Icon(Icons.bluetooth, Icon(Icons.bluetooth,
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12), const SizedBox(width: 12),
Text('蓝牙设备', style: AppTheme.titleMedium), const Text('蓝牙设备', style: AppTheme.titleMedium),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _deviceNameController, controller: _deviceNameController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: '设备名称 (用于自动连接)', labelText: '设备名称',
hintText: '输入设备名称', hintText: '输入设备名称',
labelStyle: const TextStyle(color: Colors.white70), labelStyle: const TextStyle(color: Colors.white70),
hintStyle: const TextStyle(color: Colors.white54), hintStyle: const TextStyle(color: Colors.white54),
@@ -329,14 +325,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
Icon(Icons.settings, Icon(Icons.settings,
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12), const SizedBox(width: 12),
Text('应用设置', style: AppTheme.titleMedium), const Text('应用设置', style: AppTheme.titleMedium),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('后台保活服务', style: AppTheme.bodyLarge), Text('后台保活服务', style: AppTheme.bodyLarge),
@@ -356,7 +352,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
await BackgroundService.stopService(); await BackgroundService.stopService();
} }
}, },
activeColor: Theme.of(context).colorScheme.primary, activeThumbColor: Theme.of(context).colorScheme.primary,
), ),
], ],
), ),
@@ -364,7 +360,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('通知服务', style: AppTheme.bodyLarge), Text('通知服务', style: AppTheme.bodyLarge),
@@ -378,7 +374,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
}); });
_saveSettings(); _saveSettings();
}, },
activeColor: Theme.of(context).colorScheme.primary, activeThumbColor: Theme.of(context).colorScheme.primary,
), ),
], ],
), ),
@@ -386,7 +382,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('地图组件类型', style: AppTheme.bodyLarge), Text('地图组件类型', style: AppTheme.bodyLarge),
@@ -394,7 +390,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
DropdownButton<String>( DropdownButton<String>(
value: _mapType, value: _mapType,
items: [ items: const [
DropdownMenuItem( DropdownMenuItem(
value: 'webview', value: 'webview',
child: Text('矢量铁路地图', style: AppTheme.bodyMedium), child: Text('矢量铁路地图', style: AppTheme.bodyMedium),
@@ -422,7 +418,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('隐藏只有时间有效的记录', style: AppTheme.bodyLarge), Text('隐藏只有时间有效的记录', style: AppTheme.bodyLarge),
@@ -436,7 +432,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
}); });
_saveSettings(); _saveSettings();
}, },
activeColor: Theme.of(context).colorScheme.primary, activeThumbColor: Theme.of(context).colorScheme.primary,
), ),
], ],
), ),
@@ -463,14 +459,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
Icon(Icons.merge_type, Icon(Icons.merge_type,
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12), const SizedBox(width: 12),
Text('记录合并', style: AppTheme.titleMedium), const Text('记录合并', style: AppTheme.titleMedium),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('启用记录合并', style: AppTheme.bodyLarge), Text('启用记录合并', style: AppTheme.bodyLarge),
@@ -484,7 +480,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
}); });
_saveSettings(); _saveSettings();
}, },
activeColor: Theme.of(context).colorScheme.primary, activeThumbColor: Theme.of(context).colorScheme.primary,
), ),
], ],
), ),
@@ -494,11 +490,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 16), const SizedBox(height: 16),
Text('分组方式', style: AppTheme.bodyLarge), const Text('分组方式', style: AppTheme.bodyLarge),
const SizedBox(height: 8), const SizedBox(height: 8),
DropdownButtonFormField<GroupBy>( DropdownButtonFormField<GroupBy>(
value: _groupBy, initialValue: _groupBy,
items: [ items: const [
DropdownMenuItem( DropdownMenuItem(
value: GroupBy.trainOnly, value: GroupBy.trainOnly,
child: Text('仅车次号', style: AppTheme.bodyMedium)), child: Text('仅车次号', style: AppTheme.bodyMedium)),
@@ -532,11 +528,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
style: AppTheme.bodyMedium, style: AppTheme.bodyMedium,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text('时间窗口', style: AppTheme.bodyLarge), const Text('时间窗口', style: AppTheme.bodyLarge),
const SizedBox(height: 8), const SizedBox(height: 8),
DropdownButtonFormField<TimeWindow>( DropdownButtonFormField<TimeWindow>(
value: _timeWindow, initialValue: _timeWindow,
items: [ items: const [
DropdownMenuItem( DropdownMenuItem(
value: TimeWindow.oneHour, value: TimeWindow.oneHour,
child: Text('1小时内', style: AppTheme.bodyMedium)), child: Text('1小时内', style: AppTheme.bodyMedium)),
@@ -579,7 +575,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('隐藏不可分组记录', style: AppTheme.bodyLarge), Text('隐藏不可分组记录', style: AppTheme.bodyLarge),
@@ -593,7 +589,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
}); });
_saveSettings(); _saveSettings();
}, },
activeColor: Theme.of(context).colorScheme.primary, activeThumbColor: Theme.of(context).colorScheme.primary,
), ),
], ],
), ),
@@ -623,7 +619,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Icon(Icons.storage, Icon(Icons.storage,
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12), const SizedBox(width: 12),
Text('数据管理', style: AppTheme.titleMedium), const Text('数据管理', style: AppTheme.titleMedium),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -705,7 +701,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
], ],
), ),
), ),
Icon( const Icon(
Icons.chevron_right, Icons.chevron_right,
color: Colors.white54, color: Colors.white54,
size: 20, size: 20,
@@ -760,7 +756,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
if (exportedPath != null) { if (exportedPath != null) {
final file = File(exportedPath); final file = File(exportedPath);
final fileName = file.path.split(Platform.pathSeparator).last;
await Share.shareXFiles( await Share.shareXFiles(
[XFile(file.path)], [XFile(file.path)],
@@ -954,11 +949,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [ children: [
Icon(Icons.info, color: Theme.of(context).colorScheme.primary), Icon(Icons.info, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12), const SizedBox(width: 12),
Text('关于', style: AppTheme.titleMedium), const Text('关于', style: AppTheme.titleMedium),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text('LBJ Console', style: AppTheme.titleMedium), const Text('LBJ Console', style: AppTheme.titleMedium),
const SizedBox(height: 8), const SizedBox(height: 8),
FutureBuilder<String>( FutureBuilder<String>(
future: _getAppVersion(), future: _getAppVersion(),
@@ -979,7 +974,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
await launchUrl(url); await launchUrl(url);
} }
}, },
child: Text( child: const Text(
'https://github.com/undef-i/LBJConsole', 'https://github.com/undef-i/LBJConsole',
style: AppTheme.caption, style: AppTheme.caption,
), ),

View File

@@ -3,7 +3,6 @@ import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:lbjconsole/services/ble_service.dart'; import 'package:lbjconsole/services/ble_service.dart';
@@ -107,7 +106,7 @@ class BackgroundService {
_notificationId, _notificationId,
'LBJ Console', 'LBJ Console',
'蓝牙连接监控中', '蓝牙连接监控中',
NotificationDetails( const NotificationDetails(
android: AndroidNotificationDetails( android: AndroidNotificationDetails(
_notificationChannelId, _notificationChannelId,
_notificationChannelName, _notificationChannelName,
@@ -146,7 +145,7 @@ class BackgroundService {
_notificationId, _notificationId,
'LBJ Console', 'LBJ Console',
isConnected ? '蓝牙已连接 - $deviceStatus' : '蓝牙未连接 - 自动重连中', isConnected ? '蓝牙已连接 - $deviceStatus' : '蓝牙未连接 - 自动重连中',
NotificationDetails( const NotificationDetails(
android: AndroidNotificationDetails( android: AndroidNotificationDetails(
_notificationChannelId, _notificationChannelId,
_notificationChannelName, _notificationChannelName,

View File

@@ -145,7 +145,9 @@ class BLEService {
if (isConnected || if (isConnected ||
_isConnecting || _isConnecting ||
_isManualDisconnect || _isManualDisconnect ||
_isAutoConnectBlocked) return; _isAutoConnectBlocked) {
return;
}
for (var device in allFoundDevices) { for (var device in allFoundDevices) {
if (_shouldAutoConnectTo(device)) { if (_shouldAutoConnectTo(device)) {
@@ -168,10 +170,13 @@ class BLEService {
final deviceAddress = device.remoteId.str; final deviceAddress = device.remoteId.str;
if (_targetDeviceName.isNotEmpty && if (_targetDeviceName.isNotEmpty &&
deviceName.toLowerCase() == _targetDeviceName.toLowerCase()) deviceName.toLowerCase() == _targetDeviceName.toLowerCase()) {
return true; return true;
}
if (_lastKnownDeviceAddress != null && if (_lastKnownDeviceAddress != null &&
_lastKnownDeviceAddress == deviceAddress) return true; _lastKnownDeviceAddress == deviceAddress) {
return true;
}
return false; return false;
} }

View File

@@ -27,7 +27,7 @@ class DatabaseService {
} }
_database = await _initDatabase(); _database = await _initDatabase();
return _database!; return _database!;
} catch (e, stackTrace) { } catch (e) {
rethrow; rethrow;
} }
} }
@@ -38,8 +38,6 @@ class DatabaseService {
return false; return false;
} }
final db = await database;
final result = await db.rawQuery('SELECT 1');
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
@@ -59,7 +57,7 @@ class DatabaseService {
); );
return db; return db;
} catch (e, stackTrace) { } catch (e) {
rethrow; rethrow;
} }
} }
@@ -206,7 +204,7 @@ class DatabaseService {
final records = final records =
result.map((json) => TrainRecord.fromDatabaseJson(json)).toList(); result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
return records; return records;
} catch (e, stackTrace) { } catch (e) {
rethrow; rethrow;
} }
} }
@@ -239,7 +237,7 @@ class DatabaseService {
final records = final records =
result.map((json) => TrainRecord.fromDatabaseJson(json)).toList(); result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
return records; return records;
} catch (e, stackTrace) { } catch (e) {
rethrow; rethrow;
} }
} }

View File

@@ -1,4 +1,3 @@
import 'package:lbjconsole/util/loco_type_util.dart';
class LocoTypeService { class LocoTypeService {
static final LocoTypeService _instance = LocoTypeService._internal(); static final LocoTypeService _instance = LocoTypeService._internal();

View File

@@ -111,7 +111,7 @@ class MergeService {
} }
}); });
final reusedRecords = _reuseDiscardedRecords( _reuseDiscardedRecords(
discardedRecords, mergedRecordIds, settings.groupBy); discardedRecords, mergedRecordIds, settings.groupBy);
final singleRecords = filteredRecords final singleRecords = filteredRecords
@@ -283,100 +283,4 @@ class MergeService {
return result; return result;
} }
static List<Object> _groupByTrainOrLoco(List<TrainRecord> records) {
final List<MergedTrainRecord> mergedRecords = [];
final List<TrainRecord> singleRecords = [];
final Set<String> usedRecordIds = {};
for (int i = 0; i < records.length; i++) {
final record = records[i];
if (usedRecordIds.contains(record.uniqueId)) continue;
final group = <TrainRecord>[record];
for (int j = i + 1; j < records.length; j++) {
final otherRecord = records[j];
if (usedRecordIds.contains(otherRecord.uniqueId)) continue;
final recordTrain = record.train.trim();
final otherTrain = otherRecord.train.trim();
final recordLoco = record.loco.trim();
final otherLoco = otherRecord.loco.trim();
final trainMatch = recordTrain.isNotEmpty &&
recordTrain != "<NUL>" &&
!recordTrain.contains("-----") &&
otherTrain.isNotEmpty &&
otherTrain != "<NUL>" &&
!otherTrain.contains("-----") &&
recordTrain == otherTrain;
final locoMatch = recordLoco.isNotEmpty &&
recordLoco != "<NUL>" &&
otherLoco.isNotEmpty &&
otherLoco != "<NUL>" &&
recordLoco == otherLoco;
final bothTrainEmpty = (recordTrain.isEmpty ||
recordTrain == "<NUL>" ||
recordTrain.contains("----")) &&
(otherTrain.isEmpty ||
otherTrain == "<NUL>" ||
otherTrain.contains("----"));
if (trainMatch || locoMatch || (bothTrainEmpty && locoMatch)) {
group.add(otherRecord);
}
}
if (group.length >= 2) {
for (final record in group) {
usedRecordIds.add(record.uniqueId);
}
final firstRecord = group.first;
final train = firstRecord.train.trim();
final loco = firstRecord.loco.trim();
String uniqueGroupKey;
if (train.isNotEmpty &&
train != "<NUL>" &&
!train.contains("-----") &&
loco.isNotEmpty &&
loco != "<NUL>") {
uniqueGroupKey = "train_or_loco:${train}_$loco";
} else if (train.isNotEmpty &&
train != "<NUL>" &&
!train.contains("-----")) {
uniqueGroupKey = "train_or_loco:train:$train";
} else if (loco.isNotEmpty && loco != "<NUL>") {
uniqueGroupKey = "train_or_loco:loco:$loco";
} else {
uniqueGroupKey = "train_or_loco:group_${mergedRecords.length}";
}
mergedRecords.add(MergedTrainRecord(
groupKey: uniqueGroupKey,
records: group,
latestRecord: group.first,
));
} else {
singleRecords.add(record);
usedRecordIds.add(record.uniqueId);
}
}
final List<Object> result = [...mergedRecords, ...singleRecords];
result.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 result;
}
} }

View File

@@ -20,7 +20,7 @@ class NotificationService {
const AndroidInitializationSettings initializationSettingsAndroid = const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher'); AndroidInitializationSettings('@mipmap/ic_launcher');
final InitializationSettings initializationSettings = const InitializationSettings initializationSettings =
InitializationSettings( InitializationSettings(
android: initializationSettingsAndroid, android: initializationSettingsAndroid,
); );
@@ -61,7 +61,7 @@ class NotificationService {
return; return;
} }
final String title = '列车信息'; const String title = '列车信息';
final String body = _buildNotificationContent(record); final String body = _buildNotificationContent(record);
final AndroidNotificationDetails androidPlatformChannelSpecifics = final AndroidNotificationDetails androidPlatformChannelSpecifics =

View File

@@ -149,8 +149,9 @@ class _LbJState {
break; break;
case _lbjSyncAddr: case _lbjSyncAddr:
if (numeric.length >= 5) if (numeric.length >= 5) {
time = "${numeric.substring(1, 3)}:${numeric.substring(3, 5)}"; time = "${numeric.substring(1, 3)}:${numeric.substring(3, 5)}";
}
break; break;
} }
} }
@@ -167,11 +168,11 @@ class _LbJState {
String gpsPosition = ""; String gpsPosition = "";
if (posLatDeg.isNotEmpty && posLatMin.isNotEmpty) { if (posLatDeg.isNotEmpty && posLatMin.isNotEmpty) {
gpsPosition = "${posLatDeg}°${posLatMin}"; gpsPosition = "$posLatDeg°$posLatMin";
} }
if (posLonDeg.isNotEmpty && posLonMin.isNotEmpty) { if (posLonDeg.isNotEmpty && posLonMin.isNotEmpty) {
gpsPosition += gpsPosition +=
(gpsPosition.isEmpty ? "" : " ") + "${posLonDeg}°${posLonMin}"; "${gpsPosition.isEmpty ? "" : " "}$posLonDeg°$posLonMin";
} }
String kmPosition = positionKm.replaceAll(' <NUL>', ''); String kmPosition = positionKm.replaceAll(' <NUL>', '');

View File

@@ -9,13 +9,11 @@ class AppTheme {
canvasColor: Colors.black, canvasColor: Colors.black,
cardColor: const Color(0xFF121212), cardColor: const Color(0xFF121212),
primaryColor: Colors.blue, primaryColor: Colors.blue,
colorScheme: ColorScheme.dark( colorScheme: const ColorScheme.dark(
primary: Colors.blue, primary: Colors.blue,
secondary: Colors.blueAccent, secondary: Colors.blueAccent,
surface: const Color(0xFF121212), surface: Color(0xFF121212),
background: Colors.black,
onSurface: Colors.white, onSurface: Colors.white,
onBackground: Colors.white,
), ),
appBarTheme: const AppBarTheme( appBarTheme: const AppBarTheme(
backgroundColor: Colors.black, backgroundColor: Colors.black,
@@ -67,16 +65,16 @@ class AppTheme {
thickness: 1, thickness: 1,
), ),
switchTheme: SwitchThemeData( switchTheme: SwitchThemeData(
thumbColor: MaterialStateProperty.resolveWith<Color?>( thumbColor: WidgetStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) { (Set<WidgetState> states) {
if (states.contains(MaterialState.selected)) { if (states.contains(WidgetState.selected)) {
return Colors.blue; return Colors.blue;
} }
return Colors.grey; return Colors.grey;
}), }),
trackColor: MaterialStateProperty.resolveWith<Color?>( trackColor: WidgetStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) { (Set<WidgetState> states) {
if (states.contains(MaterialState.selected)) { if (states.contains(WidgetState.selected)) {
return Colors.blue.withOpacity(0.5); return Colors.blue.withOpacity(0.5);
} }
return Colors.grey.withOpacity(0.5); return Colors.grey.withOpacity(0.5);

View File

@@ -1,4 +1,3 @@
import 'dart:convert';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class LocoInfoUtil { class LocoInfoUtil {

View File

@@ -1,4 +1,3 @@
import 'dart:convert';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class TrainTypeUtil { class TrainTypeUtil {

View File

@@ -241,6 +241,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.7.11" version: "0.7.11"
dependency_validator:
dependency: "direct dev"
description:
name: dependency_validator
sha256: "3a23914cacac37d0cdce067d0576fce18bf5951338616f036a20604c97dba0f7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.3"
executor_lib: executor_lib:
dependency: transitive dependency: transitive
description: description:
@@ -529,21 +537,13 @@ packages:
source: hosted source: hosted
version: "2.3.2" version: "2.3.2"
hive: hive:
dependency: "direct main" dependency: transitive
description: description:
name: hive name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.2.3" version: "2.2.3"
hive_flutter:
dependency: "direct main"
description:
name: hive_flutter
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
hive_generator: hive_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -688,30 +688,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
maplibre_gl:
dependency: "direct main"
description:
name: maplibre_gl
sha256: "5c7b1008396b2a321bada7d986ed60f9423406fbc7bd16f7ce91b385dfa054cd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.22.0"
maplibre_gl_platform_interface:
dependency: transitive
description:
name: maplibre_gl_platform_interface
sha256: "08ee0a2d0853ea945a0ab619d52c0c714f43144145cd67478fc6880b52f37509"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.22.0"
maplibre_gl_web:
dependency: transitive
description:
name: maplibre_gl_web
sha256: "2b13d4b1955a9a54e38a718f2324e56e4983c080fc6de316f6f4b5458baacb58"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.22.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -760,14 +736,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.16.12" version: "3.16.12"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -960,14 +928,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
provider:
dependency: "direct main"
description:
name: provider
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.5"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@@ -1028,18 +988,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" sha256: "46a46fd64659eff15f4638bbe19de43f9483f0e0bf024a9fb6b3582064bacc7b"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.4.7" version: "2.4.17"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.5.4" version: "2.5.6"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:

View File

@@ -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 # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 0.8.0-flutter+80 version: 0.8.1-flutter+81
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@@ -34,10 +34,8 @@ dependencies:
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
flutter_blue_plus: ^1.31.15 flutter_blue_plus: ^1.31.15
permission_handler: ^11.3.1 permission_handler: ^11.3.1
provider: ^6.1.2
shared_preferences: ^2.3.2 shared_preferences: ^2.3.2
hive: ^2.2.3
hive_flutter: ^1.1.0
path: ^1.9.0 path: ^1.9.0
path_provider: ^2.1.4 path_provider: ^2.1.4
intl: ^0.19.0 intl: ^0.19.0
@@ -46,6 +44,7 @@ dependencies:
latlong2: ^0.9.1 latlong2: ^0.9.1
geolocator: ^13.0.4 geolocator: ^13.0.4
geolocator_android: 4.6.1 geolocator_android: 4.6.1
url_launcher: ^6.2.5 url_launcher: ^6.2.5
sqflite: ^2.3.3+1 sqflite: ^2.3.3+1
share_plus: ^10.0.0 share_plus: ^10.0.0
@@ -55,7 +54,6 @@ dependencies:
flutter_background_service: ^5.1.0 flutter_background_service: ^5.1.0
scrollview_observer: ^1.20.0 scrollview_observer: ^1.20.0
vector_map_tiles: ^8.0.0 vector_map_tiles: ^8.0.0
maplibre_gl: ^0.22.0
webview_flutter: ^4.8.0 webview_flutter: ^4.8.0
gbk_codec: ^0.4.0 gbk_codec: ^0.4.0
@@ -72,6 +70,7 @@ dev_dependencies:
hive_generator: ^2.0.1 hive_generator: ^2.0.1
build_runner: ^2.4.6 build_runner: ^2.4.6
flutter_launcher_icons: ^0.14.1 flutter_launcher_icons: ^0.14.1
dependency_validator: ^4.1.3
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec