2 Commits

Author SHA1 Message Date
Nedifinita
06aa8491b4 feat: add user location display and improve route display 2025-10-16 15:47:22 +08:00
Nedifinita
6073ea615e fix: solve the problem that errors may occur during merging 2025-10-14 23:55:42 +08:00
4 changed files with 295 additions and 164 deletions

View File

@@ -221,28 +221,43 @@ class HistoryScreenState extends State<HistoryScreen> {
if (mounted) { if (mounted) {
if (_isAtTop) { if (_isAtTop) {
setState(() { setState(() {
bool isMerge = false; List<TrainRecord> allRecords = [];
Object? mergeResult; Set<String> selectedRecordIds = {};
if (_displayItems.isNotEmpty) {
final firstItem = _displayItems.first; for (final item in _displayItems) {
List<TrainRecord> tempRecords = [newRecord]; if (item is MergedTrainRecord) {
if (firstItem is MergedTrainRecord) { allRecords.addAll(item.records);
tempRecords.addAll(firstItem.records); if (_selectedRecords.contains(item.records.first.uniqueId)) {
} else if (firstItem is TrainRecord) { selectedRecordIds.addAll(item.records.map((r) => r.uniqueId));
tempRecords.add(firstItem); }
} } else if (item is TrainRecord) {
final mergeCheckResult = allRecords.add(item);
MergeService.getMixedList(tempRecords, _mergeSettings); if (_selectedRecords.contains(item.uniqueId)) {
if (mergeCheckResult.length == 1 && selectedRecordIds.add(item.uniqueId);
mergeCheckResult.first is MergedTrainRecord) { }
isMerge = true;
mergeResult = mergeCheckResult.first;
} }
} }
if (isMerge) {
_displayItems[0] = mergeResult!; allRecords.insert(0, newRecord);
} else {
_displayItems.insert(0, newRecord); final mergedItems =
MergeService.getMixedList(allRecords, _mergeSettings);
_displayItems.clear();
_displayItems.addAll(mergedItems);
_selectedRecords.clear();
for (final item in _displayItems) {
if (item is MergedTrainRecord) {
if (item.records
.any((r) => selectedRecordIds.contains(r.uniqueId))) {
_selectedRecords.addAll(item.records.map((r) => r.uniqueId));
}
} else if (item is TrainRecord) {
if (selectedRecordIds.contains(item.uniqueId)) {
_selectedRecords.add(item.uniqueId);
}
}
} }
}); });
if (_scrollController.hasClients) { if (_scrollController.hasClients) {
@@ -723,8 +738,6 @@ class HistoryScreenState extends State<HistoryScreen> {
Widget _buildRecordCard(TrainRecord record, Widget _buildRecordCard(TrainRecord record,
{bool isSubCard = false, Key? key}) { {bool isSubCard = false, Key? key}) {
final isSelected = _selectedRecords.contains(record.uniqueId); final isSelected = _selectedRecords.contains(record.uniqueId);
final isExpanded =
!isSubCard && (_expandedStates[record.uniqueId] ?? false);
return Card( return Card(
key: key, key: key,
@@ -752,6 +765,11 @@ class HistoryScreenState extends State<HistoryScreen> {
} }
widget.onSelectionChanged(); widget.onSelectionChanged();
}); });
} else {
setState(() {
_expandedStates[record.uniqueId] =
!(_expandedStates[record.uniqueId] ?? false);
});
} }
}, },
onLongPress: () { onLongPress: () {
@@ -771,7 +789,8 @@ class HistoryScreenState extends State<HistoryScreen> {
_buildRecordHeader(record), _buildRecordHeader(record),
_buildPositionAndSpeed(record), _buildPositionAndSpeed(record),
_buildLocoInfo(record), _buildLocoInfo(record),
if (isExpanded) _buildExpandedContent(record), if (_expandedStates[record.uniqueId] ?? false)
_buildExpandedContent(record),
])))); ]))));
} }
@@ -803,6 +822,7 @@ class HistoryScreenState extends State<HistoryScreen> {
final hasLocoInfo = final hasLocoInfo =
formattedLocoInfo.isNotEmpty && formattedLocoInfo != "<NUL>"; formattedLocoInfo.isNotEmpty && formattedLocoInfo != "<NUL>";
final shouldShowTrainRow = hasTrainNumber || hasDirection || hasLocoInfo; final shouldShowTrainRow = hasTrainNumber || hasDirection || hasLocoInfo;
final hasPosition = _parsePosition(record.positionInfo) != null;
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
@@ -856,7 +876,8 @@ class HistoryScreenState extends State<HistoryScreen> {
])), ])),
if (hasLocoInfo) if (hasLocoInfo)
Text(formattedLocoInfo, Text(formattedLocoInfo,
style: const TextStyle(fontSize: 14, color: Colors.white70)) style:
const TextStyle(fontSize: 14, color: Colors.white70)),
]), ]),
const SizedBox(height: 2) const SizedBox(height: 2)
] ]

View File

@@ -51,7 +51,10 @@ class _MapScreenState extends State<MapScreen> {
_loadSettings().then((_) { _loadSettings().then((_) {
_loadTrainRecords().then((_) { _loadTrainRecords().then((_) {
_startLocationUpdates(); _startLocationUpdates();
if (!_isMapInitialized && (_currentLocation != null || _lastTrainLocation != null || _userLocation != null)) { if (!_isMapInitialized &&
(_currentLocation != null ||
_lastTrainLocation != null ||
_userLocation != null)) {
_initializeMapPosition(); _initializeMapPosition();
} }
}); });
@@ -418,8 +421,6 @@ class _MapScreenState extends State<MapScreen> {
fontSize: 8, fontSize: 8,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
overflow: TextOverflow.ellipsis,
maxLines: 1,
), ),
), ),
], ],
@@ -746,8 +747,8 @@ class _MapScreenState extends State<MapScreen> {
} }
final bool isDefaultLocation = _currentLocation == null && final bool isDefaultLocation = _currentLocation == null &&
_lastTrainLocation == null && _lastTrainLocation == null &&
_userLocation == null; _userLocation == null;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFF121212), backgroundColor: const Color(0xFF121212),
@@ -759,7 +760,8 @@ class _MapScreenState extends State<MapScreen> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
CircularProgressIndicator( CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF007ACC)), valueColor:
AlwaysStoppedAnimation<Color>(Color(0xFF007ACC)),
), ),
SizedBox(height: 16), SizedBox(height: 16),
Text( Text(
@@ -769,44 +771,45 @@ class _MapScreenState extends State<MapScreen> {
], ],
), ),
) )
else FlutterMap( else
mapController: _mapController, FlutterMap(
options: MapOptions( mapController: _mapController,
initialCenter: _currentLocation ?? options: MapOptions(
_lastTrainLocation ?? initialCenter: _currentLocation ??
_userLocation ?? _lastTrainLocation ??
const LatLng(39.9042, 116.4074), _userLocation ??
initialZoom: _currentZoom, const LatLng(39.9042, 116.4074),
initialRotation: _currentRotation, initialZoom: _currentZoom,
minZoom: 8.0, initialRotation: _currentRotation,
maxZoom: 18.0, minZoom: 2.0,
onPositionChanged: (MapCamera camera, bool hasGesture) { maxZoom: 18.0,
setState(() { onPositionChanged: (MapCamera camera, bool hasGesture) {
_currentLocation = camera.center; setState(() {
_currentZoom = camera.zoom; _currentLocation = camera.center;
_currentRotation = camera.rotation; _currentZoom = camera.zoom;
}); _currentRotation = camera.rotation;
});
_saveSettings(); _saveSettings();
}, },
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'org.noxylva.lbjconsole',
), ),
if (_railwayLayerVisible) children: [
TileLayer( TileLayer(
urlTemplate: urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
'https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
subdomains: const ['a', 'b', 'c'],
userAgentPackageName: 'org.noxylva.lbjconsole', userAgentPackageName: 'org.noxylva.lbjconsole',
), ),
MarkerLayer( if (_railwayLayerVisible)
markers: markers, TileLayer(
), urlTemplate:
], 'https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
), subdomains: const ['a', 'b', 'c'],
userAgentPackageName: 'org.noxylva.lbjconsole.flutter',
),
MarkerLayer(
markers: markers,
),
],
),
if (_isLoading) if (_isLoading)
const Center( const Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(

View File

@@ -2,6 +2,7 @@ import 'dart:async';
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';
import 'package:geolocator/geolocator.dart';
import '../models/merged_record.dart'; import '../models/merged_record.dart';
import '../services/database_service.dart'; import '../services/database_service.dart';
import '../models/train_record.dart'; import '../models/train_record.dart';
@@ -30,6 +31,10 @@ class RealtimeScreenState extends State<RealtimeScreen> {
List<Marker> _mapMarkers = []; List<Marker> _mapMarkers = [];
bool _showMap = true; bool _showMap = true;
Set<String> _selectedGroupKeys = {}; Set<String> _selectedGroupKeys = {};
LatLng? _userLocation;
bool _isLocationPermissionGranted = false;
Timer? _locationTimer;
StreamSubscription<Position>? _positionStreamSubscription;
List<Object> getDisplayItems() => _displayItems; List<Object> getDisplayItems() => _displayItems;
@@ -85,6 +90,28 @@ class RealtimeScreenState extends State<RealtimeScreen> {
.where((marker) => marker != null) .where((marker) => marker != null)
.cast<Marker>() .cast<Marker>()
.toList(); .toList();
if (_userLocation != null) {
_mapMarkers.add(
Marker(
point: _userLocation!,
width: 24,
height: 24,
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white, width: 1),
),
child: const Icon(
Icons.my_location,
color: Colors.white,
size: 12,
),
),
),
);
}
}); });
} }
@@ -142,7 +169,6 @@ class RealtimeScreenState extends State<RealtimeScreen> {
} }
} catch (e) { } catch (e) {
_selectedGroupKeys.remove(groupKey); _selectedGroupKeys.remove(groupKey);
print('记录不存在,移除选中状态: $groupKey');
} }
} }
@@ -167,19 +193,29 @@ class RealtimeScreenState extends State<RealtimeScreen> {
markers: [ markers: [
Marker( Marker(
point: position, point: position,
width: 60, width: 80,
height: 20, height: 16,
child: Container( child: Column(
color: Colors.black, mainAxisSize: MainAxisSize.min,
alignment: Alignment.center, mainAxisAlignment: MainAxisAlignment.center,
child: Text( children: [
_getTrainDisplayName(singleRecord), Container(
style: const TextStyle( padding: const EdgeInsets.symmetric(
color: Colors.white, horizontal: 6, vertical: 2),
fontSize: 10, decoration: BoxDecoration(
fontWeight: FontWeight.bold, color: Colors.black.withOpacity(0.8),
borderRadius: BorderRadius.circular(3),
),
child: Text(
_getTrainDisplayName(singleRecord),
style: const TextStyle(
color: Colors.white,
fontSize: 8,
fontWeight: FontWeight.bold,
),
),
), ),
), ],
), ),
), ),
], ],
@@ -205,19 +241,29 @@ class RealtimeScreenState extends State<RealtimeScreen> {
markers: [ markers: [
Marker( Marker(
point: routePoints.last, point: routePoints.last,
width: 60, width: 80,
height: 20, height: 16,
child: Container( child: Column(
color: Colors.black, mainAxisSize: MainAxisSize.min,
alignment: Alignment.center, mainAxisAlignment: MainAxisAlignment.center,
child: Text( children: [
_getTrainDisplayName(mergedRecord.latestRecord), Container(
style: const TextStyle( padding: const EdgeInsets.symmetric(
color: Colors.white, horizontal: 6, vertical: 2),
fontSize: 10, decoration: BoxDecoration(
fontWeight: FontWeight.bold, color: Colors.black.withOpacity(0.8),
borderRadius: BorderRadius.circular(3),
),
child: Text(
_getTrainDisplayName(mergedRecord.latestRecord),
style: const TextStyle(
color: Colors.white,
fontSize: 8,
fontWeight: FontWeight.bold,
),
),
), ),
), ],
), ),
), ),
], ],
@@ -227,7 +273,6 @@ class RealtimeScreenState extends State<RealtimeScreen> {
} }
} catch (e) { } catch (e) {
_selectedGroupKeys.remove(groupKey); _selectedGroupKeys.remove(groupKey);
print('记录不存在,移除选中状态: $groupKey');
} }
} }
@@ -275,7 +320,6 @@ class RealtimeScreenState extends State<RealtimeScreen> {
} }
} catch (e) { } catch (e) {
_selectedGroupKeys.remove(groupKey); _selectedGroupKeys.remove(groupKey);
print('记录不存在,移除选中状态: $groupKey');
} }
} }
@@ -460,25 +504,19 @@ class RealtimeScreenState extends State<RealtimeScreen> {
void initState() { void initState() {
super.initState(); super.initState();
_scrollController.addListener(() { _scrollController.addListener(() {
print(
'滚动监听器触发 - 当前位置: ${_scrollController.position.pixels}, maxScrollExtent: ${_scrollController.position.maxScrollExtent}, isAtTop: $_isAtTop');
if (_scrollController.position.atEdge) { if (_scrollController.position.atEdge) {
if (_scrollController.position.pixels == if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) { _scrollController.position.maxScrollExtent) {
print('到达底部(反转后的"顶部"- 设置 _isAtTop = true');
if (!_isAtTop) { if (!_isAtTop) {
setState(() => _isAtTop = true); setState(() => _isAtTop = true);
} }
} else if (_scrollController.position.pixels == 0) { } else if (_scrollController.position.pixels == 0) {
print('到达顶部(反转后的"底部"- 设置 _isAtTop = false');
if (_isAtTop) { if (_isAtTop) {
setState(() => _isAtTop = false); setState(() => _isAtTop = false);
} }
} }
} else { } else {
if (_isAtTop) { if (_isAtTop) {
print('离开底部(反转后的"顶部"- 设置 _isAtTop = false');
setState(() => _isAtTop = false); setState(() => _isAtTop = false);
} }
} }
@@ -494,6 +532,7 @@ class RealtimeScreenState extends State<RealtimeScreen> {
}); });
_setupRecordDeleteListener(); _setupRecordDeleteListener();
_setupSettingsListener(); _setupSettingsListener();
_startLocationUpdates();
} }
void _scheduleInitialScroll() { void _scheduleInitialScroll() {
@@ -501,16 +540,12 @@ class RealtimeScreenState extends State<RealtimeScreen> {
if (mounted && _scrollController.hasClients && _displayItems.isNotEmpty) { if (mounted && _scrollController.hasClients && _displayItems.isNotEmpty) {
try { try {
final maxScrollExtent = _scrollController.position.maxScrollExtent; final maxScrollExtent = _scrollController.position.maxScrollExtent;
print('初始滚动执行maxScrollExtent=$maxScrollExtent');
_scrollController.jumpTo(maxScrollExtent); _scrollController.jumpTo(maxScrollExtent);
print('初始滚动完成:位置=${_scrollController.position.pixels}');
if (!_isAtTop) { if (!_isAtTop) {
setState(() => _isAtTop = true); setState(() => _isAtTop = true);
} }
} catch (e) { } catch (e) {}
print('初始滚动错误:$e');
}
} }
}); });
} }
@@ -520,6 +555,8 @@ class RealtimeScreenState extends State<RealtimeScreen> {
_scrollController.dispose(); _scrollController.dispose();
_recordDeleteSubscription?.cancel(); _recordDeleteSubscription?.cancel();
_settingsSubscription?.cancel(); _settingsSubscription?.cancel();
_locationTimer?.cancel();
_positionStreamSubscription?.cancel();
super.dispose(); super.dispose();
} }
@@ -541,6 +578,70 @@ class RealtimeScreenState extends State<RealtimeScreen> {
}); });
} }
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();
_startRealtimeLocationUpdates();
}
Future<void> _getCurrentLocation() async {
try {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
forceAndroidLocationManager: true,
);
final newLocation = LatLng(position.latitude, position.longitude);
setState(() {
_userLocation = newLocation;
});
_updateAllRecordMarkers();
} catch (e) {}
}
void _startLocationUpdates() {
_requestLocationPermission();
}
void _startRealtimeLocationUpdates() {
_positionStreamSubscription?.cancel();
_positionStreamSubscription = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 1,
timeLimit: Duration(seconds: 30),
),
).listen(
(Position position) {
final newLocation = LatLng(position.latitude, position.longitude);
setState(() {
_userLocation = newLocation;
});
_updateAllRecordMarkers();
},
onError: (error) {},
);
}
Future<void> loadRecords({bool scrollToTop = true}) async { Future<void> loadRecords({bool scrollToTop = true}) async {
try { try {
if (mounted) { if (mounted) {
@@ -675,13 +776,8 @@ class RealtimeScreenState extends State<RealtimeScreen> {
try { try {
final maxScrollExtent = final maxScrollExtent =
_scrollController.position.maxScrollExtent; _scrollController.position.maxScrollExtent;
print('loadRecords - 滚动到底部, maxScrollExtent: $maxScrollExtent');
_scrollController.jumpTo(maxScrollExtent); _scrollController.jumpTo(maxScrollExtent);
print( } catch (e) {}
'loadRecords - 滚动完成,新位置: ${_scrollController.position.pixels}');
} catch (e) {
print('loadRecords - 滚动错误: $e');
}
} }
} else { } else {
if (_isLoading) { if (_isLoading) {
@@ -697,11 +793,9 @@ class RealtimeScreenState extends State<RealtimeScreen> {
} }
Future<void> addNewRecord(TrainRecord newRecord) async { Future<void> addNewRecord(TrainRecord newRecord) async {
print('addNewRecord - 开始添加新记录, 当前_isAtTop=$_isAtTop');
try { try {
final position = _parsePositionFromRecord(newRecord); final position = _parsePositionFromRecord(newRecord);
if (position == null) { if (position == null) {
print('addNewRecord - 记录没有位置信息,忽略');
return; return;
} }
@@ -721,41 +815,44 @@ class RealtimeScreenState extends State<RealtimeScreen> {
if (!isNewRecord) return; if (!isNewRecord) return;
if (mounted) { if (mounted) {
setState(() { List<TrainRecord> allRecords = [];
bool isMerge = false; Set<String> selectedRecordIds = {};
Object? mergeResult;
String? oldSingleRecordKey;
if (_displayItems.isNotEmpty) { for (final item in _displayItems) {
final firstItem = _displayItems.first; if (item is MergedTrainRecord) {
List<TrainRecord> tempRecords = [newRecord]; allRecords.addAll(item.records);
if (firstItem is MergedTrainRecord) { if (_selectedGroupKeys.contains(item.groupKey)) {
tempRecords.addAll(firstItem.records); selectedRecordIds.addAll(item.records.map((r) => r.uniqueId));
} else if (firstItem is TrainRecord) {
tempRecords.add(firstItem);
oldSingleRecordKey = "single:${firstItem.uniqueId}";
} }
final mergeCheckResult = } else if (item is TrainRecord) {
MergeService.getMixedList(tempRecords, _mergeSettings); allRecords.add(item);
if (mergeCheckResult.length == 1 && if (_selectedGroupKeys.contains("single:${item.uniqueId}")) {
mergeCheckResult.first is MergedTrainRecord) { selectedRecordIds.add(item.uniqueId);
isMerge = true;
mergeResult = mergeCheckResult.first;
} }
} }
}
if (isMerge) { allRecords.insert(0, newRecord);
final mergedRecord = mergeResult as MergedTrainRecord;
_displayItems[0] = mergedRecord;
if (oldSingleRecordKey != null && final mergedItems =
_selectedGroupKeys.contains(oldSingleRecordKey)) { MergeService.getMixedList(allRecords, _mergeSettings);
_selectedGroupKeys.remove(oldSingleRecordKey);
_selectedGroupKeys.add(mergedRecord.groupKey); setState(() {
_displayItems.clear();
_displayItems.addAll(mergedItems);
_selectedGroupKeys.clear();
for (final item in _displayItems) {
if (item is MergedTrainRecord) {
if (item.records
.any((r) => selectedRecordIds.contains(r.uniqueId))) {
_selectedGroupKeys.add(item.groupKey);
}
} else if (item is TrainRecord) {
if (selectedRecordIds.contains(item.uniqueId)) {
_selectedGroupKeys.add("single:${item.uniqueId}");
}
} }
} else {
_displayItems.insert(0, newRecord);
} }
}); });
@@ -765,23 +862,16 @@ class RealtimeScreenState extends State<RealtimeScreen> {
_adjustMapViewToSelectedGroups(); _adjustMapViewToSelectedGroups();
} }
print(
'addNewRecord - 检查滚动条件: _isAtTop=$_isAtTop, hasClients=${_scrollController.hasClients}, 当前位置: ${_scrollController.position.pixels}');
if (_isAtTop && _scrollController.hasClients) { if (_isAtTop && _scrollController.hasClients) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _scrollController.hasClients) { if (mounted && _scrollController.hasClients) {
final newMaxScrollExtent = final newMaxScrollExtent =
_scrollController.position.maxScrollExtent; _scrollController.position.maxScrollExtent;
print(
'addNewRecord - 执行滚动到底部, maxScrollExtent: $newMaxScrollExtent');
_scrollController.jumpTo(newMaxScrollExtent); _scrollController.jumpTo(newMaxScrollExtent);
print(
'addNewRecord - 滚动完成,新位置: ${_scrollController.position.pixels}');
} }
}); });
} else { } else {}
print('addNewRecord - 不执行滚动,条件不满足');
}
} }
} catch (e) {} } catch (e) {}
} }
@@ -1193,13 +1283,23 @@ class RealtimeScreenState extends State<RealtimeScreen> {
} }
final latestRoute = getValidRoute(latestRecord); final latestRoute = getValidRoute(latestRecord);
final previousRoute =
previousRecord != null ? getValidRoute(previousRecord) : "";
final bool needsSpecialDisplay = previousRecord != null && String displayRoute = latestRoute;
latestRoute.isNotEmpty && bool isDisplayingLatestNormal = true;
previousRoute.isNotEmpty &&
latestRoute != previousRoute; if (latestRoute.isEmpty || latestRoute.contains('*')) {
for (final record in mergedRecord.records) {
final route = getValidRoute(record);
if (route.isNotEmpty && !route.contains('*')) {
displayRoute = route;
isDisplayingLatestNormal = (record == latestRecord);
break;
}
}
}
final bool needsSpecialDisplay = !isDisplayingLatestNormal ||
(latestRoute.contains('*') && displayRoute != latestRoute);
final position = latestRecord.position.trim(); final position = latestRecord.position.trim();
final speed = latestRecord.speed.trim(); final speed = latestRecord.speed.trim();
@@ -1227,10 +1327,10 @@ class RealtimeScreenState extends State<RealtimeScreen> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (latestRoute.isNotEmpty) ...[ if (displayRoute.isNotEmpty) ...[
if (needsSpecialDisplay) ...[ if (needsSpecialDisplay) ...[
Flexible( Flexible(
child: Text(previousRoute, child: Text(displayRoute,
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
color: Colors.white, color: Colors.white,
@@ -1245,18 +1345,25 @@ class RealtimeScreenState extends State<RealtimeScreen> {
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: const Color(0xFF1E1E1E), backgroundColor: const Color(0xFF1E1E1E),
title: const Text("路线变化", title: const Text("路线信息",
style: TextStyle(color: Colors.white)), style: TextStyle(color: Colors.white)),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("上一条: $previousRoute", if (!isDisplayingLatestNormal) ...[
style: const TextStyle(color: Colors.grey)), Text("显示路线: $displayRoute",
const SizedBox(height: 8), style:
Text("当前: $latestRoute", const TextStyle(color: Colors.white)),
style: const SizedBox(height: 8),
const TextStyle(color: Colors.white)), ],
Text(
"最新路线: ${latestRoute.isNotEmpty ? latestRoute : '无效路线'}",
style: TextStyle(
color: latestRoute.isNotEmpty
? Colors.grey
: Colors.red,
)),
], ],
), ),
actions: [ actions: [
@@ -1285,7 +1392,7 @@ class RealtimeScreenState extends State<RealtimeScreen> {
const SizedBox(width: 4), const SizedBox(width: 4),
] else ] else
Flexible( Flexible(
child: Text(latestRoute, child: Text(displayRoute,
style: const TextStyle( style: const TextStyle(
fontSize: 16, color: Colors.white), fontSize: 16, color: Colors.white),
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis)),

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.6.0-flutter+60 version: 0.7.0-flutter+70
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4