Compare commits
2 Commits
v0.6.0-flu
...
v0.7.0-flu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06aa8491b4 | ||
|
|
6073ea615e |
@@ -221,28 +221,43 @@ class HistoryScreenState extends State<HistoryScreen> {
|
||||
if (mounted) {
|
||||
if (_isAtTop) {
|
||||
setState(() {
|
||||
bool isMerge = false;
|
||||
Object? mergeResult;
|
||||
if (_displayItems.isNotEmpty) {
|
||||
final firstItem = _displayItems.first;
|
||||
List<TrainRecord> tempRecords = [newRecord];
|
||||
if (firstItem is MergedTrainRecord) {
|
||||
tempRecords.addAll(firstItem.records);
|
||||
} else if (firstItem is TrainRecord) {
|
||||
tempRecords.add(firstItem);
|
||||
}
|
||||
final mergeCheckResult =
|
||||
MergeService.getMixedList(tempRecords, _mergeSettings);
|
||||
if (mergeCheckResult.length == 1 &&
|
||||
mergeCheckResult.first is MergedTrainRecord) {
|
||||
isMerge = true;
|
||||
mergeResult = mergeCheckResult.first;
|
||||
List<TrainRecord> allRecords = [];
|
||||
Set<String> selectedRecordIds = {};
|
||||
|
||||
for (final item in _displayItems) {
|
||||
if (item is MergedTrainRecord) {
|
||||
allRecords.addAll(item.records);
|
||||
if (_selectedRecords.contains(item.records.first.uniqueId)) {
|
||||
selectedRecordIds.addAll(item.records.map((r) => r.uniqueId));
|
||||
}
|
||||
} else if (item is TrainRecord) {
|
||||
allRecords.add(item);
|
||||
if (_selectedRecords.contains(item.uniqueId)) {
|
||||
selectedRecordIds.add(item.uniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMerge) {
|
||||
_displayItems[0] = mergeResult!;
|
||||
} else {
|
||||
_displayItems.insert(0, newRecord);
|
||||
|
||||
allRecords.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) {
|
||||
@@ -723,8 +738,6 @@ class HistoryScreenState extends State<HistoryScreen> {
|
||||
Widget _buildRecordCard(TrainRecord record,
|
||||
{bool isSubCard = false, Key? key}) {
|
||||
final isSelected = _selectedRecords.contains(record.uniqueId);
|
||||
final isExpanded =
|
||||
!isSubCard && (_expandedStates[record.uniqueId] ?? false);
|
||||
|
||||
return Card(
|
||||
key: key,
|
||||
@@ -752,6 +765,11 @@ class HistoryScreenState extends State<HistoryScreen> {
|
||||
}
|
||||
widget.onSelectionChanged();
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_expandedStates[record.uniqueId] =
|
||||
!(_expandedStates[record.uniqueId] ?? false);
|
||||
});
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
@@ -771,7 +789,8 @@ class HistoryScreenState extends State<HistoryScreen> {
|
||||
_buildRecordHeader(record),
|
||||
_buildPositionAndSpeed(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 =
|
||||
formattedLocoInfo.isNotEmpty && formattedLocoInfo != "<NUL>";
|
||||
final shouldShowTrainRow = hasTrainNumber || hasDirection || hasLocoInfo;
|
||||
final hasPosition = _parsePosition(record.positionInfo) != null;
|
||||
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
@@ -856,7 +876,8 @@ class HistoryScreenState extends State<HistoryScreen> {
|
||||
])),
|
||||
if (hasLocoInfo)
|
||||
Text(formattedLocoInfo,
|
||||
style: const TextStyle(fontSize: 14, color: Colors.white70))
|
||||
style:
|
||||
const TextStyle(fontSize: 14, color: Colors.white70)),
|
||||
]),
|
||||
const SizedBox(height: 2)
|
||||
]
|
||||
|
||||
@@ -51,7 +51,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
_loadSettings().then((_) {
|
||||
_loadTrainRecords().then((_) {
|
||||
_startLocationUpdates();
|
||||
if (!_isMapInitialized && (_currentLocation != null || _lastTrainLocation != null || _userLocation != null)) {
|
||||
if (!_isMapInitialized &&
|
||||
(_currentLocation != null ||
|
||||
_lastTrainLocation != null ||
|
||||
_userLocation != null)) {
|
||||
_initializeMapPosition();
|
||||
}
|
||||
});
|
||||
@@ -418,8 +421,6 @@ class _MapScreenState extends State<MapScreen> {
|
||||
fontSize: 8,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -746,8 +747,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
}
|
||||
|
||||
final bool isDefaultLocation = _currentLocation == null &&
|
||||
_lastTrainLocation == null &&
|
||||
_userLocation == null;
|
||||
_lastTrainLocation == null &&
|
||||
_userLocation == null;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF121212),
|
||||
@@ -759,7 +760,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF007ACC)),
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(Color(0xFF007ACC)),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
@@ -769,44 +771,45 @@ class _MapScreenState extends State<MapScreen> {
|
||||
],
|
||||
),
|
||||
)
|
||||
else FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: _currentLocation ??
|
||||
_lastTrainLocation ??
|
||||
_userLocation ??
|
||||
const LatLng(39.9042, 116.4074),
|
||||
initialZoom: _currentZoom,
|
||||
initialRotation: _currentRotation,
|
||||
minZoom: 8.0,
|
||||
maxZoom: 18.0,
|
||||
onPositionChanged: (MapCamera camera, bool hasGesture) {
|
||||
setState(() {
|
||||
_currentLocation = camera.center;
|
||||
_currentZoom = camera.zoom;
|
||||
_currentRotation = camera.rotation;
|
||||
});
|
||||
else
|
||||
FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: _currentLocation ??
|
||||
_lastTrainLocation ??
|
||||
_userLocation ??
|
||||
const LatLng(39.9042, 116.4074),
|
||||
initialZoom: _currentZoom,
|
||||
initialRotation: _currentRotation,
|
||||
minZoom: 2.0,
|
||||
maxZoom: 18.0,
|
||||
onPositionChanged: (MapCamera camera, bool hasGesture) {
|
||||
setState(() {
|
||||
_currentLocation = camera.center;
|
||||
_currentZoom = camera.zoom;
|
||||
_currentRotation = camera.rotation;
|
||||
});
|
||||
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
userAgentPackageName: 'org.noxylva.lbjconsole',
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
if (_railwayLayerVisible)
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
'https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
|
||||
subdomains: const ['a', 'b', 'c'],
|
||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
userAgentPackageName: 'org.noxylva.lbjconsole',
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: markers,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_railwayLayerVisible)
|
||||
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)
|
||||
const Center(
|
||||
child: CircularProgressIndicator(
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import '../models/merged_record.dart';
|
||||
import '../services/database_service.dart';
|
||||
import '../models/train_record.dart';
|
||||
@@ -30,6 +31,10 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
List<Marker> _mapMarkers = [];
|
||||
bool _showMap = true;
|
||||
Set<String> _selectedGroupKeys = {};
|
||||
LatLng? _userLocation;
|
||||
bool _isLocationPermissionGranted = false;
|
||||
Timer? _locationTimer;
|
||||
StreamSubscription<Position>? _positionStreamSubscription;
|
||||
|
||||
List<Object> getDisplayItems() => _displayItems;
|
||||
|
||||
@@ -85,6 +90,28 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
.where((marker) => marker != null)
|
||||
.cast<Marker>()
|
||||
.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) {
|
||||
_selectedGroupKeys.remove(groupKey);
|
||||
print('记录不存在,移除选中状态: $groupKey');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,19 +193,29 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
markers: [
|
||||
Marker(
|
||||
point: position,
|
||||
width: 60,
|
||||
height: 20,
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_getTrainDisplayName(singleRecord),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
width: 80,
|
||||
height: 16,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
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: [
|
||||
Marker(
|
||||
point: routePoints.last,
|
||||
width: 60,
|
||||
height: 20,
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_getTrainDisplayName(mergedRecord.latestRecord),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
width: 80,
|
||||
height: 16,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
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) {
|
||||
_selectedGroupKeys.remove(groupKey);
|
||||
print('记录不存在,移除选中状态: $groupKey');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +320,6 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
}
|
||||
} catch (e) {
|
||||
_selectedGroupKeys.remove(groupKey);
|
||||
print('记录不存在,移除选中状态: $groupKey');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,25 +504,19 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(() {
|
||||
print(
|
||||
'滚动监听器触发 - 当前位置: ${_scrollController.position.pixels}, maxScrollExtent: ${_scrollController.position.maxScrollExtent}, isAtTop: $_isAtTop');
|
||||
|
||||
if (_scrollController.position.atEdge) {
|
||||
if (_scrollController.position.pixels ==
|
||||
_scrollController.position.maxScrollExtent) {
|
||||
print('到达底部(反转后的"顶部")- 设置 _isAtTop = true');
|
||||
if (!_isAtTop) {
|
||||
setState(() => _isAtTop = true);
|
||||
}
|
||||
} else if (_scrollController.position.pixels == 0) {
|
||||
print('到达顶部(反转后的"底部")- 设置 _isAtTop = false');
|
||||
if (_isAtTop) {
|
||||
setState(() => _isAtTop = false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_isAtTop) {
|
||||
print('离开底部(反转后的"顶部")- 设置 _isAtTop = false');
|
||||
setState(() => _isAtTop = false);
|
||||
}
|
||||
}
|
||||
@@ -494,6 +532,7 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
});
|
||||
_setupRecordDeleteListener();
|
||||
_setupSettingsListener();
|
||||
_startLocationUpdates();
|
||||
}
|
||||
|
||||
void _scheduleInitialScroll() {
|
||||
@@ -501,16 +540,12 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
if (mounted && _scrollController.hasClients && _displayItems.isNotEmpty) {
|
||||
try {
|
||||
final maxScrollExtent = _scrollController.position.maxScrollExtent;
|
||||
print('初始滚动执行:maxScrollExtent=$maxScrollExtent');
|
||||
_scrollController.jumpTo(maxScrollExtent);
|
||||
print('初始滚动完成:位置=${_scrollController.position.pixels}');
|
||||
|
||||
if (!_isAtTop) {
|
||||
setState(() => _isAtTop = true);
|
||||
}
|
||||
} catch (e) {
|
||||
print('初始滚动错误:$e');
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -520,6 +555,8 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
_scrollController.dispose();
|
||||
_recordDeleteSubscription?.cancel();
|
||||
_settingsSubscription?.cancel();
|
||||
_locationTimer?.cancel();
|
||||
_positionStreamSubscription?.cancel();
|
||||
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 {
|
||||
try {
|
||||
if (mounted) {
|
||||
@@ -675,13 +776,8 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
try {
|
||||
final maxScrollExtent =
|
||||
_scrollController.position.maxScrollExtent;
|
||||
print('loadRecords - 滚动到底部, maxScrollExtent: $maxScrollExtent');
|
||||
_scrollController.jumpTo(maxScrollExtent);
|
||||
print(
|
||||
'loadRecords - 滚动完成,新位置: ${_scrollController.position.pixels}');
|
||||
} catch (e) {
|
||||
print('loadRecords - 滚动错误: $e');
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
} else {
|
||||
if (_isLoading) {
|
||||
@@ -697,11 +793,9 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
}
|
||||
|
||||
Future<void> addNewRecord(TrainRecord newRecord) async {
|
||||
print('addNewRecord - 开始添加新记录, 当前_isAtTop=$_isAtTop');
|
||||
try {
|
||||
final position = _parsePositionFromRecord(newRecord);
|
||||
if (position == null) {
|
||||
print('addNewRecord - 记录没有位置信息,忽略');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -721,41 +815,44 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
if (!isNewRecord) return;
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
bool isMerge = false;
|
||||
Object? mergeResult;
|
||||
String? oldSingleRecordKey;
|
||||
List<TrainRecord> allRecords = [];
|
||||
Set<String> selectedRecordIds = {};
|
||||
|
||||
if (_displayItems.isNotEmpty) {
|
||||
final firstItem = _displayItems.first;
|
||||
List<TrainRecord> tempRecords = [newRecord];
|
||||
if (firstItem is MergedTrainRecord) {
|
||||
tempRecords.addAll(firstItem.records);
|
||||
} else if (firstItem is TrainRecord) {
|
||||
tempRecords.add(firstItem);
|
||||
|
||||
oldSingleRecordKey = "single:${firstItem.uniqueId}";
|
||||
for (final item in _displayItems) {
|
||||
if (item is MergedTrainRecord) {
|
||||
allRecords.addAll(item.records);
|
||||
if (_selectedGroupKeys.contains(item.groupKey)) {
|
||||
selectedRecordIds.addAll(item.records.map((r) => r.uniqueId));
|
||||
}
|
||||
final mergeCheckResult =
|
||||
MergeService.getMixedList(tempRecords, _mergeSettings);
|
||||
if (mergeCheckResult.length == 1 &&
|
||||
mergeCheckResult.first is MergedTrainRecord) {
|
||||
isMerge = true;
|
||||
mergeResult = mergeCheckResult.first;
|
||||
} else if (item is TrainRecord) {
|
||||
allRecords.add(item);
|
||||
if (_selectedGroupKeys.contains("single:${item.uniqueId}")) {
|
||||
selectedRecordIds.add(item.uniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMerge) {
|
||||
final mergedRecord = mergeResult as MergedTrainRecord;
|
||||
_displayItems[0] = mergedRecord;
|
||||
allRecords.insert(0, newRecord);
|
||||
|
||||
if (oldSingleRecordKey != null &&
|
||||
_selectedGroupKeys.contains(oldSingleRecordKey)) {
|
||||
_selectedGroupKeys.remove(oldSingleRecordKey);
|
||||
_selectedGroupKeys.add(mergedRecord.groupKey);
|
||||
final mergedItems =
|
||||
MergeService.getMixedList(allRecords, _mergeSettings);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
print(
|
||||
'addNewRecord - 检查滚动条件: _isAtTop=$_isAtTop, hasClients=${_scrollController.hasClients}, 当前位置: ${_scrollController.position.pixels}');
|
||||
if (_isAtTop && _scrollController.hasClients) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted && _scrollController.hasClients) {
|
||||
final newMaxScrollExtent =
|
||||
_scrollController.position.maxScrollExtent;
|
||||
print(
|
||||
'addNewRecord - 执行滚动到底部, maxScrollExtent: $newMaxScrollExtent');
|
||||
|
||||
_scrollController.jumpTo(newMaxScrollExtent);
|
||||
print(
|
||||
'addNewRecord - 滚动完成,新位置: ${_scrollController.position.pixels}');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
print('addNewRecord - 不执行滚动,条件不满足');
|
||||
}
|
||||
} else {}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -1193,13 +1283,23 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
}
|
||||
|
||||
final latestRoute = getValidRoute(latestRecord);
|
||||
final previousRoute =
|
||||
previousRecord != null ? getValidRoute(previousRecord) : "";
|
||||
|
||||
final bool needsSpecialDisplay = previousRecord != null &&
|
||||
latestRoute.isNotEmpty &&
|
||||
previousRoute.isNotEmpty &&
|
||||
latestRoute != previousRoute;
|
||||
String displayRoute = latestRoute;
|
||||
bool isDisplayingLatestNormal = true;
|
||||
|
||||
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 speed = latestRecord.speed.trim();
|
||||
@@ -1227,10 +1327,10 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (latestRoute.isNotEmpty) ...[
|
||||
if (displayRoute.isNotEmpty) ...[
|
||||
if (needsSpecialDisplay) ...[
|
||||
Flexible(
|
||||
child: Text(previousRoute,
|
||||
child: Text(displayRoute,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
@@ -1245,18 +1345,25 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: const Color(0xFF1E1E1E),
|
||||
title: const Text("路线变化",
|
||||
title: const Text("路线信息",
|
||||
style: TextStyle(color: Colors.white)),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("上一条: $previousRoute",
|
||||
style: const TextStyle(color: Colors.grey)),
|
||||
const SizedBox(height: 8),
|
||||
Text("当前: $latestRoute",
|
||||
style:
|
||||
const TextStyle(color: Colors.white)),
|
||||
if (!isDisplayingLatestNormal) ...[
|
||||
Text("显示路线: $displayRoute",
|
||||
style:
|
||||
const TextStyle(color: Colors.white)),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
Text(
|
||||
"最新路线: ${latestRoute.isNotEmpty ? latestRoute : '无效路线'}",
|
||||
style: TextStyle(
|
||||
color: latestRoute.isNotEmpty
|
||||
? Colors.grey
|
||||
: Colors.red,
|
||||
)),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
@@ -1285,7 +1392,7 @@ class RealtimeScreenState extends State<RealtimeScreen> {
|
||||
const SizedBox(width: 4),
|
||||
] else
|
||||
Flexible(
|
||||
child: Text(latestRoute,
|
||||
child: Text(displayRoute,
|
||||
style: const TextStyle(
|
||||
fontSize: 16, color: Colors.white),
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
|
||||
@@ -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.6.0-flutter+60
|
||||
version: 0.7.0-flutter+70
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
|
||||
Reference in New Issue
Block a user