This commit is contained in:
Nedifinita
2025-11-23 21:43:47 +08:00
parent fe2769f479
commit a47c6a5745
20 changed files with 88 additions and 209 deletions

View File

@@ -340,22 +340,6 @@ class HistoryScreenState extends State<HistoryScreen> {
} 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) {
if (_displayItems.length != newItems.length) return true;

View File

@@ -3,8 +3,6 @@ import 'package:flutter/services.dart';
import 'dart:async';
import 'dart:developer' as developer;
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/map_webview_screen.dart';
@@ -307,17 +305,17 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
developer.log('rtl_tcp: setup_listener');
_settingsSubscription =
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) {
final rtlTcpEnabled = (settings?['rtlTcpEnabled'] ?? 0) == 1;
final rtlTcpEnabled = (settings['rtlTcpEnabled'] ?? 0) == 1;
if (rtlTcpEnabled != _rtlTcpEnabled) {
setState(() {
_rtlTcpEnabled = rtlTcpEnabled;
});
if (rtlTcpEnabled) {
final host = settings?['rtlTcpHost']?.toString() ?? '127.0.0.1';
final port = settings?['rtlTcpPort']?.toString() ?? '14423';
final host = settings['rtlTcpHost']?.toString() ?? '127.0.0.1';
final port = settings['rtlTcpPort']?.toString() ?? '14423';
_connectToRtlTcp(host, port);
} else {
_rtlTcpConnectionSubscription?.cancel();
@@ -661,11 +659,12 @@ class _PixelPerfectBluetoothDialogState
Future<void> _startScan() async {
if (_scanState == _ScanState.scanning) return;
if (mounted)
if (mounted) {
setState(() {
_devices.clear();
_scanState = _ScanState.scanning;
});
}
await widget.bleService.startScan(
timeout: const Duration(seconds: 8),
onScanResults: (devices) {
@@ -784,7 +783,7 @@ class _PixelPerfectBluetoothDialogState
.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text('$currentAddress',
Text(currentAddress,
style: TextStyle(color: isConnected ? Colors.green : Colors.grey)),
const SizedBox(height: 16),
if (_lastReceivedTime != null && isConnected) ...[

View File

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

View File

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

View File

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

View File

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