refactor: optimize record card rendering logic and animation effects

This commit is contained in:
Nedifinita
2025-09-25 22:01:25 +08:00
parent 56689fc993
commit bf850eed38

View File

@@ -1,6 +1,9 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:isolate'; import 'dart:isolate';
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.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:scrollview_observer/scrollview_observer.dart'; import 'package:scrollview_observer/scrollview_observer.dart';
@@ -140,10 +143,13 @@ class HistoryScreenState extends State<HistoryScreen> {
_chatObserver.standby(); _chatObserver.standby();
} }
setState(() { final hasDataChanged = _hasDataChanged(items);
_displayItems.clear(); if (hasDataChanged) {
_displayItems.addAll(items); setState(() {
}); _displayItems.clear();
_displayItems.addAll(items);
});
}
if (_isAtTop && _scrollController.hasClients) { if (_isAtTop && _scrollController.hasClients) {
_scrollController.jumpTo(0.0); _scrollController.jumpTo(0.0);
@@ -198,7 +204,7 @@ class HistoryScreenState extends State<HistoryScreen> {
if (item is MergedTrainRecord) { if (item is MergedTrainRecord) {
return _buildMergedRecordCard(item); return _buildMergedRecordCard(item);
} else if (item is TrainRecord) { } else if (item is TrainRecord) {
return _buildRecordCard(item); return _buildRecordCard(item, key: ValueKey(item.uniqueId));
} }
return const SizedBox.shrink(); return const SizedBox.shrink();
}, },
@@ -503,7 +509,8 @@ class HistoryScreenState extends State<HistoryScreen> {
return 10.0; return 10.0;
} }
Widget _buildRecordCard(TrainRecord record, {bool isSubCard = false}) { Widget _buildRecordCard(TrainRecord record,
{bool isSubCard = false, Key? key}) {
final isSelected = _selectedRecords.contains(record.uniqueId); final isSelected = _selectedRecords.contains(record.uniqueId);
final isExpanded = final isExpanded =
!isSubCard && (_expandedStates[record.uniqueId] ?? false); !isSubCard && (_expandedStates[record.uniqueId] ?? false);
@@ -511,7 +518,7 @@ class HistoryScreenState extends State<HistoryScreen> {
final GlobalKey itemKey = GlobalKey(); final GlobalKey itemKey = GlobalKey();
final Widget card = Card( final Widget card = Card(
key: itemKey, key: key ?? itemKey,
color: isSelected && _isEditMode color: isSelected && _isEditMode
? const Color(0xFF2E2E2E) ? const Color(0xFF2E2E2E)
: const Color(0xFF1E1E1E), : const Color(0xFF1E1E1E),
@@ -538,15 +545,23 @@ class HistoryScreenState extends State<HistoryScreen> {
}); });
} else if (!isSubCard) { } else if (!isSubCard) {
if (isExpanded) { if (isExpanded) {
setState(() { final shouldUpdate =
_expandedStates[record.uniqueId] = false; _expandedStates[record.uniqueId] == true ||
_mapOptimalZoom.remove(record.uniqueId); _mapOptimalZoom.containsKey(record.uniqueId) ||
_mapCalculating.remove(record.uniqueId); _mapCalculating.containsKey(record.uniqueId);
}); if (shouldUpdate) {
setState(() {
_expandedStates[record.uniqueId] = false;
_mapOptimalZoom.remove(record.uniqueId);
_mapCalculating.remove(record.uniqueId);
});
}
} else { } else {
setState(() { if (_expandedStates[record.uniqueId] != true) {
_expandedStates[record.uniqueId] = true; setState(() {
}); _expandedStates[record.uniqueId] = true;
});
}
} }
} }
}, },
@@ -565,7 +580,17 @@ class HistoryScreenState extends State<HistoryScreen> {
_buildRecordHeader(record), _buildRecordHeader(record),
_buildPositionAndSpeed(record), _buildPositionAndSpeed(record),
_buildLocoInfo(record), _buildLocoInfo(record),
if (isExpanded) _buildExpandedContent(record) AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
firstChild: const SizedBox.shrink(),
secondChild: _buildExpandedContent(record),
crossFadeState: isExpanded
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstCurve: Curves.easeInOut,
secondCurve: Curves.easeInOut,
sizeCurve: Curves.easeInOut,
)
])))); ]))));
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -586,8 +611,6 @@ class HistoryScreenState extends State<HistoryScreen> {
Widget _buildRecordHeader(TrainRecord record, {bool isMerged = false}) { Widget _buildRecordHeader(TrainRecord record, {bool isMerged = false}) {
final trainType = record.trainType; final trainType = record.trainType;
final trainDisplay =
record.fullTrainNumber.isEmpty ? "未知列车" : record.fullTrainNumber;
String formattedLocoInfo = ""; String formattedLocoInfo = "";
if (record.locoType.isNotEmpty && record.loco.isNotEmpty) { if (record.locoType.isNotEmpty && record.loco.isNotEmpty) {
final shortLoco = record.loco.length > 5 final shortLoco = record.loco.length > 5
@@ -599,6 +622,16 @@ class HistoryScreenState extends State<HistoryScreen> {
} else if (record.loco.isNotEmpty) { } else if (record.loco.isNotEmpty) {
formattedLocoInfo = record.loco; formattedLocoInfo = record.loco;
} }
if (record.fullTrainNumber.isEmpty) {
return Text(
(record.time == "<NUL>" || record.time.isEmpty)
? record.receivedTimestamp.toString().split(".")[0]
: record.time.split("\n")[0],
style: const TextStyle(fontSize: 11, color: Colors.grey),
overflow: TextOverflow.ellipsis);
}
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Flexible( Flexible(
@@ -625,7 +658,7 @@ class HistoryScreenState extends State<HistoryScreen> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Flexible( Flexible(
child: Text(trainDisplay, child: Text(record.fullTrainNumber,
style: const TextStyle( style: const TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,