feat: add vector railway map
This commit is contained in:
@@ -18,11 +18,12 @@ class MergeSettings {
|
||||
final bool enabled;
|
||||
final GroupBy groupBy;
|
||||
final TimeWindow timeWindow;
|
||||
|
||||
final bool hideUngroupableRecords;
|
||||
MergeSettings({
|
||||
this.enabled = true,
|
||||
this.groupBy = GroupBy.trainAndLoco,
|
||||
this.timeWindow = TimeWindow.unlimited,
|
||||
this.hideUngroupableRecords = false,
|
||||
});
|
||||
|
||||
factory MergeSettings.fromMap(Map<String, dynamic> map) {
|
||||
@@ -36,6 +37,7 @@ class MergeSettings {
|
||||
(e) => e.name == map['timeWindow'],
|
||||
orElse: () => TimeWindow.unlimited,
|
||||
),
|
||||
hideUngroupableRecords: (map['hideUngroupableRecords'] ?? 0) == 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1383,7 +1383,7 @@ class _DelayedMultiMarkerMapState extends State<_DelayedMultiMarkerMap> {
|
||||
return FlutterMap(
|
||||
options: MapOptions(
|
||||
onPositionChanged: (position, hasGesture) => _onCameraMove(),
|
||||
minZoom: 5,
|
||||
minZoom: 8,
|
||||
maxZoom: 18,
|
||||
),
|
||||
mapController: _mapController,
|
||||
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
import 'package:lbjconsole/screens/settings_screen.dart';
|
||||
import 'package:lbjconsole/services/ble_service.dart';
|
||||
import 'package:lbjconsole/services/database_service.dart';
|
||||
@@ -174,6 +175,7 @@ class MainScreen extends StatefulWidget {
|
||||
|
||||
class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
int _currentIndex = 0;
|
||||
String _mapType = 'webview';
|
||||
|
||||
late final BLEService _bleService;
|
||||
final NotificationService _notificationService = NotificationService();
|
||||
@@ -195,8 +197,19 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
_initializeServices();
|
||||
_checkAndStartBackgroundService();
|
||||
_setupLastReceivedTimeListener();
|
||||
_loadMapType();
|
||||
}
|
||||
|
||||
Future<void> _loadMapType() async {
|
||||
final settings = await DatabaseService.instance.getAllSettings() ?? {};
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_mapType = settings['mapType']?.toString() ?? 'webview';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _checkAndStartBackgroundService() async {
|
||||
final settings = await DatabaseService.instance.getAllSettings() ?? {};
|
||||
final backgroundServiceEnabled =
|
||||
@@ -231,6 +244,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
_bleService.onAppResume();
|
||||
_loadMapType();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,8 +394,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
onEditModeChanged: _handleHistoryEditModeChanged,
|
||||
onSelectionChanged: _handleSelectionChanged,
|
||||
),
|
||||
const MapScreen(),
|
||||
const SettingsScreen(),
|
||||
_mapType == 'map' ? const MapScreen() : const MapWebViewScreen(),
|
||||
SettingsScreen(
|
||||
onSettingsChanged: () {
|
||||
_loadMapType();
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
@@ -399,6 +417,10 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
||||
if (_currentIndex == 2 && index == 0) {
|
||||
_historyScreenKey.currentState?.reloadRecords();
|
||||
}
|
||||
// 如果从设置页面切换到地图页面,重新加载地图类型
|
||||
if (_currentIndex == 2 && index == 1) {
|
||||
_loadMapType();
|
||||
}
|
||||
setState(() {
|
||||
if (_isHistoryEditMode) _isHistoryEditMode = false;
|
||||
_currentIndex = index;
|
||||
|
||||
@@ -22,7 +22,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
LatLng? _currentLocation;
|
||||
LatLng? _lastTrainLocation;
|
||||
LatLng? _userLocation;
|
||||
double _currentZoom = 12.0;
|
||||
double _currentZoom = 14.0;
|
||||
double _currentRotation = 0.0;
|
||||
|
||||
bool _isMapInitialized = false;
|
||||
@@ -51,10 +51,19 @@ class _MapScreenState extends State<MapScreen> {
|
||||
_loadSettings().then((_) {
|
||||
_loadTrainRecords().then((_) {
|
||||
_startLocationUpdates();
|
||||
if (!_isMapInitialized && (_currentLocation != null || _lastTrainLocation != null || _userLocation != null)) {
|
||||
_initializeMapPosition();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
Future<void> _checkDatabaseSettings() async {
|
||||
try {
|
||||
final dbInfo = await DatabaseService.instance.getDatabaseInfo();
|
||||
@@ -227,6 +236,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
settings['mapCenterLon'] = center.longitude;
|
||||
}
|
||||
|
||||
settings['mapSettingsTimestamp'] = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
await DatabaseService.instance.updateSettings(settings);
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -734,11 +745,31 @@ class _MapScreenState extends State<MapScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
final bool isDefaultLocation = _currentLocation == null &&
|
||||
_lastTrainLocation == null &&
|
||||
_userLocation == null;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF121212),
|
||||
body: Stack(
|
||||
children: [
|
||||
FlutterMap(
|
||||
if (isDefaultLocation)
|
||||
const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF007ACC)),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'正在加载地图位置...',
|
||||
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: _currentLocation ??
|
||||
@@ -747,7 +778,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
const LatLng(39.9042, 116.4074),
|
||||
initialZoom: _currentZoom,
|
||||
initialRotation: _currentRotation,
|
||||
minZoom: 4.0,
|
||||
minZoom: 8.0,
|
||||
maxZoom: 18.0,
|
||||
onPositionChanged: (MapCamera camera, bool hasGesture) {
|
||||
setState(() {
|
||||
|
||||
1162
lib/screens/map_webview_screen.dart
Normal file
1162
lib/screens/map_webview_screen.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,9 @@ import 'package:share_plus/share_plus.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({super.key});
|
||||
final VoidCallback? onSettingsChanged;
|
||||
|
||||
const SettingsScreen({super.key, this.onSettingsChanged});
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
@@ -33,8 +35,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
int _recordCount = 0;
|
||||
bool _mergeRecordsEnabled = false;
|
||||
bool _hideTimeOnlyRecords = false;
|
||||
bool _hideUngroupableRecords = false;
|
||||
GroupBy _groupBy = GroupBy.trainAndLoco;
|
||||
TimeWindow _timeWindow = TimeWindow.unlimited;
|
||||
String _mapType = 'map';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -63,8 +67,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_notificationsEnabled = (settingsMap['notificationEnabled'] ?? 1) == 1;
|
||||
_mergeRecordsEnabled = settings.enabled;
|
||||
_hideTimeOnlyRecords = (settingsMap['hideTimeOnlyRecords'] ?? 0) == 1;
|
||||
_hideUngroupableRecords = settings.hideUngroupableRecords;
|
||||
_groupBy = settings.groupBy;
|
||||
_timeWindow = settings.timeWindow;
|
||||
_mapType = settingsMap['mapType']?.toString() ?? 'webview';
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -85,9 +91,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
'notificationEnabled': _notificationsEnabled ? 1 : 0,
|
||||
'mergeRecordsEnabled': _mergeRecordsEnabled ? 1 : 0,
|
||||
'hideTimeOnlyRecords': _hideTimeOnlyRecords ? 1 : 0,
|
||||
'hideUngroupableRecords': _hideUngroupableRecords ? 1 : 0,
|
||||
'groupBy': _groupBy.name,
|
||||
'timeWindow': _timeWindow.name,
|
||||
'mapType': _mapType,
|
||||
});
|
||||
widget.onSettingsChanged?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -240,6 +249,43 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('地图显示方式', style: AppTheme.bodyLarge),
|
||||
Text('选择地图组件类型', style: AppTheme.caption),
|
||||
],
|
||||
),
|
||||
DropdownButton<String>(
|
||||
value: _mapType,
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: 'webview',
|
||||
child: Text('矢量铁路地图', style: AppTheme.bodyMedium),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 'map',
|
||||
child: Text('栅格铁路地图', style: AppTheme.bodyMedium),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
_mapType = value;
|
||||
});
|
||||
_saveSettings();
|
||||
}
|
||||
},
|
||||
dropdownColor: AppTheme.secondaryBlack,
|
||||
style: AppTheme.bodyMedium,
|
||||
underline: Container(height: 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -398,6 +444,29 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
dropdownColor: AppTheme.secondaryBlack,
|
||||
style: AppTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('隐藏不可分组记录', style: AppTheme.bodyLarge),
|
||||
Text('不显示无法分组的记录', style: AppTheme.caption),
|
||||
],
|
||||
),
|
||||
Switch(
|
||||
value: _hideUngroupableRecords,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_hideUngroupableRecords = value;
|
||||
});
|
||||
_saveSettings();
|
||||
},
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -421,7 +490,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.storage, color: Theme.of(context).colorScheme.primary),
|
||||
Icon(Icons.storage,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
Text('数据管理', style: AppTheme.titleMedium),
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ class DatabaseService {
|
||||
DatabaseService._internal();
|
||||
|
||||
static const String _databaseName = 'train_database';
|
||||
static const _databaseVersion = 4;
|
||||
static const _databaseVersion = 6;
|
||||
|
||||
static const String trainRecordsTable = 'train_records';
|
||||
static const String appSettingsTable = 'app_settings';
|
||||
@@ -21,21 +21,47 @@ class DatabaseService {
|
||||
Database? _database;
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database!;
|
||||
_database = await _initDatabase();
|
||||
return _database!;
|
||||
try {
|
||||
if (_database != null) {
|
||||
return _database!;
|
||||
}
|
||||
_database = await _initDatabase();
|
||||
return _database!;
|
||||
} catch (e, stackTrace) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> isDatabaseConnected() async {
|
||||
try {
|
||||
if (_database == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final db = await database;
|
||||
final result = await db.rawQuery('SELECT 1');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final path = join(directory.path, _databaseName);
|
||||
try {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final path = join(directory.path, _databaseName);
|
||||
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
onUpgrade: _onUpgrade,
|
||||
);
|
||||
final db = await openDatabase(
|
||||
path,
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
onUpgrade: _onUpgrade,
|
||||
);
|
||||
|
||||
return db;
|
||||
} catch (e, stackTrace) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||
@@ -51,8 +77,15 @@ class DatabaseService {
|
||||
try {
|
||||
await db.execute(
|
||||
'ALTER TABLE $appSettingsTable ADD COLUMN mapTimeFilter TEXT NOT NULL DEFAULT "unlimited"');
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
await db.execute(
|
||||
'ALTER TABLE $appSettingsTable ADD COLUMN mapType TEXT NOT NULL DEFAULT "webview"');
|
||||
}
|
||||
if (oldVersion < 6) {
|
||||
await db.execute(
|
||||
'ALTER TABLE $appSettingsTable ADD COLUMN hideUngroupableRecords INTEGER NOT NULL DEFAULT 0');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +125,7 @@ class DatabaseService {
|
||||
mapZoomLevel REAL NOT NULL DEFAULT 10.0,
|
||||
mapRailwayLayerVisible INTEGER NOT NULL DEFAULT 1,
|
||||
mapRotation REAL NOT NULL DEFAULT 0.0,
|
||||
mapType TEXT NOT NULL DEFAULT 'webview',
|
||||
specifiedDeviceAddress TEXT,
|
||||
searchOrderList TEXT NOT NULL DEFAULT '',
|
||||
autoConnectEnabled INTEGER NOT NULL DEFAULT 1,
|
||||
@@ -101,7 +135,8 @@ class DatabaseService {
|
||||
hideTimeOnlyRecords INTEGER NOT NULL DEFAULT 0,
|
||||
groupBy TEXT NOT NULL DEFAULT 'trainAndLoco',
|
||||
timeWindow TEXT NOT NULL DEFAULT 'unlimited',
|
||||
mapTimeFilter TEXT NOT NULL DEFAULT 'unlimited'
|
||||
mapTimeFilter TEXT NOT NULL DEFAULT 'unlimited',
|
||||
hideUngroupableRecords INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
''');
|
||||
|
||||
@@ -118,6 +153,7 @@ class DatabaseService {
|
||||
'mapZoomLevel': 10.0,
|
||||
'mapRailwayLayerVisible': 1,
|
||||
'mapRotation': 0.0,
|
||||
'mapType': 'webview',
|
||||
'searchOrderList': '',
|
||||
'autoConnectEnabled': 1,
|
||||
'backgroundServiceEnabled': 0,
|
||||
@@ -127,6 +163,7 @@ class DatabaseService {
|
||||
'groupBy': 'trainAndLoco',
|
||||
'timeWindow': 'unlimited',
|
||||
'mapTimeFilter': 'unlimited',
|
||||
'hideUngroupableRecords': 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,12 +177,18 @@ class DatabaseService {
|
||||
}
|
||||
|
||||
Future<List<TrainRecord>> getAllRecords() async {
|
||||
final db = await database;
|
||||
final result = await db.query(
|
||||
trainRecordsTable,
|
||||
orderBy: 'timestamp DESC',
|
||||
);
|
||||
return result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
|
||||
try {
|
||||
final db = await database;
|
||||
final result = await db.query(
|
||||
trainRecordsTable,
|
||||
orderBy: 'timestamp DESC',
|
||||
);
|
||||
final records =
|
||||
result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
|
||||
return records;
|
||||
} catch (e, stackTrace) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<TrainRecord>> getRecordsWithinTimeRange(Duration duration) async {
|
||||
@@ -162,15 +205,23 @@ class DatabaseService {
|
||||
|
||||
Future<List<TrainRecord>> getRecordsWithinReceivedTimeRange(
|
||||
Duration duration) async {
|
||||
final db = await database;
|
||||
final cutoffTime = DateTime.now().subtract(duration).millisecondsSinceEpoch;
|
||||
final result = await db.query(
|
||||
trainRecordsTable,
|
||||
where: 'receivedTimestamp >= ?',
|
||||
whereArgs: [cutoffTime],
|
||||
orderBy: 'receivedTimestamp DESC',
|
||||
);
|
||||
return result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
|
||||
try {
|
||||
final db = await database;
|
||||
final cutoffTime =
|
||||
DateTime.now().subtract(duration).millisecondsSinceEpoch;
|
||||
|
||||
final result = await db.query(
|
||||
trainRecordsTable,
|
||||
where: 'receivedTimestamp >= ?',
|
||||
whereArgs: [cutoffTime],
|
||||
orderBy: 'receivedTimestamp DESC',
|
||||
);
|
||||
final records =
|
||||
result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
|
||||
return records;
|
||||
} catch (e, stackTrace) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> deleteRecord(String uniqueId) async {
|
||||
|
||||
@@ -2,6 +2,37 @@ import 'package:lbjconsole/models/train_record.dart';
|
||||
import 'package:lbjconsole/models/merged_record.dart';
|
||||
|
||||
class MergeService {
|
||||
static bool isNeverGroupableRecord(TrainRecord record, GroupBy groupBy) {
|
||||
final train = record.train.trim();
|
||||
final loco = record.loco.trim();
|
||||
|
||||
final hasValidTrain =
|
||||
train.isNotEmpty && train != "<NUL>" && !train.contains("-----");
|
||||
final hasValidLoco = loco.isNotEmpty && loco != "<NUL>";
|
||||
|
||||
switch (groupBy) {
|
||||
case GroupBy.trainOnly:
|
||||
return !hasValidTrain;
|
||||
|
||||
case GroupBy.locoOnly:
|
||||
return !hasValidLoco;
|
||||
|
||||
case GroupBy.trainAndLoco:
|
||||
return !hasValidTrain || !hasValidLoco;
|
||||
|
||||
case GroupBy.trainOrLoco:
|
||||
return !hasValidTrain && !hasValidLoco;
|
||||
}
|
||||
}
|
||||
|
||||
static List<TrainRecord> filterUngroupableRecords(
|
||||
List<TrainRecord> records, GroupBy groupBy, bool hideUngroupable) {
|
||||
if (!hideUngroupable) return records;
|
||||
return records
|
||||
.where((record) => !isNeverGroupableRecord(record, groupBy))
|
||||
.toList();
|
||||
}
|
||||
|
||||
static String? _generateGroupKey(TrainRecord record, GroupBy groupBy) {
|
||||
final train = record.train.trim();
|
||||
final loco = record.loco.trim();
|
||||
@@ -36,15 +67,19 @@ class MergeService {
|
||||
return allRecords;
|
||||
}
|
||||
|
||||
allRecords
|
||||
final filteredRecords = filterUngroupableRecords(
|
||||
allRecords, settings.groupBy, settings.hideUngroupableRecords);
|
||||
|
||||
filteredRecords
|
||||
.sort((a, b) => b.receivedTimestamp.compareTo(a.receivedTimestamp));
|
||||
|
||||
if (settings.groupBy == GroupBy.trainOrLoco) {
|
||||
return _groupByTrainOrLocoWithTimeWindow(allRecords, settings.timeWindow);
|
||||
return _groupByTrainOrLocoWithTimeWindow(
|
||||
filteredRecords, settings.timeWindow);
|
||||
}
|
||||
|
||||
final groupedRecords = <String, List<TrainRecord>>{};
|
||||
for (final record in allRecords) {
|
||||
for (final record in filteredRecords) {
|
||||
final key = _generateGroupKey(record, settings.groupBy);
|
||||
if (key != null) {
|
||||
groupedRecords.putIfAbsent(key, () => []).add(record);
|
||||
@@ -79,8 +114,9 @@ class MergeService {
|
||||
final reusedRecords = _reuseDiscardedRecords(
|
||||
discardedRecords, mergedRecordIds, settings.groupBy);
|
||||
|
||||
final singleRecords =
|
||||
allRecords.where((r) => !mergedRecordIds.contains(r.uniqueId)).toList();
|
||||
final singleRecords = filteredRecords
|
||||
.where((r) => !mergedRecordIds.contains(r.uniqueId))
|
||||
.toList();
|
||||
|
||||
final List<Object> mixedList = [...mergedRecords, ...singleRecords];
|
||||
mixedList.sort((a, b) {
|
||||
@@ -219,7 +255,6 @@ class MergeService {
|
||||
latestRecord: processedGroup.first,
|
||||
));
|
||||
} else {
|
||||
// 处理被丢弃的记录
|
||||
for (final record in group) {
|
||||
if (!processedGroup.contains(record)) {
|
||||
singleRecords.add(record);
|
||||
|
||||
Reference in New Issue
Block a user