feat: add map time filtering function and optimized location processing
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/scheduler.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:geolocator/geolocator.dart';
|
||||||
import 'package:scrollview_observer/scrollview_observer.dart';
|
import 'package:scrollview_observer/scrollview_observer.dart';
|
||||||
import '../models/merged_record.dart';
|
import '../models/merged_record.dart';
|
||||||
import '../services/database_service.dart';
|
import '../services/database_service.dart';
|
||||||
@@ -45,6 +46,10 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
final Map<String, double> _mapOptimalZoom = {};
|
final Map<String, double> _mapOptimalZoom = {};
|
||||||
final Map<String, bool> _mapCalculating = {};
|
final Map<String, bool> _mapCalculating = {};
|
||||||
|
|
||||||
|
LatLng? _currentUserLocation;
|
||||||
|
bool _isLocationPermissionGranted = false;
|
||||||
|
Timer? _locationTimer;
|
||||||
|
|
||||||
int getSelectedCount() => _selectedRecords.length;
|
int getSelectedCount() => _selectedRecords.length;
|
||||||
Set<String> getSelectedRecordIds() => _selectedRecords;
|
Set<String> getSelectedRecordIds() => _selectedRecords;
|
||||||
List<Object> getDisplayItems() => _displayItems;
|
List<Object> getDisplayItems() => _displayItems;
|
||||||
@@ -81,7 +86,10 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted) loadRecords();
|
if (mounted) {
|
||||||
|
loadRecords();
|
||||||
|
_startLocationUpdates();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +97,7 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
_observerController.controller?.dispose();
|
_observerController.controller?.dispose();
|
||||||
|
_locationTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,6 +669,7 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
center: bounds.center,
|
center: bounds.center,
|
||||||
zoom: zoomLevel,
|
zoom: zoomLevel,
|
||||||
groupKey: groupKey,
|
groupKey: groupKey,
|
||||||
|
currentUserLocation: _currentUserLocation,
|
||||||
))
|
))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -670,6 +680,53 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
return 10.0;
|
return 10.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getCurrentLocation() async {
|
||||||
|
try {
|
||||||
|
Position position = await Geolocator.getCurrentPosition(
|
||||||
|
desiredAccuracy: LocationAccuracy.high,
|
||||||
|
forceAndroidLocationManager: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_currentUserLocation = LatLng(position.latitude, position.longitude);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('获取当前位置失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startLocationUpdates() {
|
||||||
|
_requestLocationPermission();
|
||||||
|
|
||||||
|
_locationTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
|
||||||
|
if (_isLocationPermissionGranted) {
|
||||||
|
_getCurrentLocation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -704,26 +761,6 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
}
|
}
|
||||||
widget.onSelectionChanged();
|
widget.onSelectionChanged();
|
||||||
});
|
});
|
||||||
} else if (!isSubCard) {
|
|
||||||
if (isExpanded) {
|
|
||||||
final shouldUpdate =
|
|
||||||
_expandedStates[record.uniqueId] == true ||
|
|
||||||
_mapOptimalZoom.containsKey(record.uniqueId) ||
|
|
||||||
_mapCalculating.containsKey(record.uniqueId);
|
|
||||||
if (shouldUpdate) {
|
|
||||||
setState(() {
|
|
||||||
_expandedStates[record.uniqueId] = false;
|
|
||||||
_mapOptimalZoom.remove(record.uniqueId);
|
|
||||||
_mapCalculating.remove(record.uniqueId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (_expandedStates[record.uniqueId] != true) {
|
|
||||||
setState(() {
|
|
||||||
_expandedStates[record.uniqueId] = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
@@ -961,6 +998,7 @@ class HistoryScreenState extends State<HistoryScreen> {
|
|||||||
position: position,
|
position: position,
|
||||||
zoom: zoomLevel,
|
zoom: zoomLevel,
|
||||||
recordId: record.uniqueId,
|
recordId: record.uniqueId,
|
||||||
|
currentUserLocation: _currentUserLocation,
|
||||||
))
|
))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -1101,12 +1139,14 @@ class _DelayedMapWithMarker extends StatefulWidget {
|
|||||||
final LatLng position;
|
final LatLng position;
|
||||||
final double zoom;
|
final double zoom;
|
||||||
final String recordId;
|
final String recordId;
|
||||||
|
final LatLng? currentUserLocation;
|
||||||
|
|
||||||
const _DelayedMapWithMarker({
|
const _DelayedMapWithMarker({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.position,
|
required this.position,
|
||||||
required this.zoom,
|
required this.zoom,
|
||||||
required this.recordId,
|
required this.recordId,
|
||||||
|
this.currentUserLocation,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1164,6 +1204,44 @@ class _DelayedMapWithMarkerState extends State<_DelayedMapWithMarker> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final markers = <Marker>[
|
||||||
|
Marker(
|
||||||
|
point: widget.position,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.white, width: 1.5),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.train, color: Colors.white, size: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (widget.currentUserLocation != null) {
|
||||||
|
markers.add(
|
||||||
|
Marker(
|
||||||
|
point: widget.currentUserLocation!,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 1.5),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.my_location,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (_isInitializing) {
|
if (_isInitializing) {
|
||||||
return FlutterMap(
|
return FlutterMap(
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
@@ -1176,19 +1254,7 @@ class _DelayedMapWithMarkerState extends State<_DelayedMapWithMarker> {
|
|||||||
TileLayer(
|
TileLayer(
|
||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
userAgentPackageName: 'org.noxylva.lbjconsole'),
|
userAgentPackageName: 'org.noxylva.lbjconsole'),
|
||||||
MarkerLayer(markers: [
|
MarkerLayer(markers: markers),
|
||||||
Marker(
|
|
||||||
point: widget.position,
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(color: Colors.white, width: 2)),
|
|
||||||
child:
|
|
||||||
const Icon(Icons.train, color: Colors.white, size: 20)))
|
|
||||||
])
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1202,19 +1268,7 @@ class _DelayedMapWithMarkerState extends State<_DelayedMapWithMarker> {
|
|||||||
TileLayer(
|
TileLayer(
|
||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
userAgentPackageName: 'org.noxylva.lbjconsole'),
|
userAgentPackageName: 'org.noxylva.lbjconsole'),
|
||||||
MarkerLayer(markers: [
|
MarkerLayer(markers: markers),
|
||||||
Marker(
|
|
||||||
point: widget.position,
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(color: Colors.white, width: 2)),
|
|
||||||
child:
|
|
||||||
const Icon(Icons.train, color: Colors.white, size: 20)))
|
|
||||||
])
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1225,6 +1279,7 @@ class _DelayedMultiMarkerMap extends StatefulWidget {
|
|||||||
final LatLng center;
|
final LatLng center;
|
||||||
final double zoom;
|
final double zoom;
|
||||||
final String groupKey;
|
final String groupKey;
|
||||||
|
final LatLng? currentUserLocation;
|
||||||
|
|
||||||
const _DelayedMultiMarkerMap({
|
const _DelayedMultiMarkerMap({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -1232,6 +1287,7 @@ class _DelayedMultiMarkerMap extends StatefulWidget {
|
|||||||
required this.center,
|
required this.center,
|
||||||
required this.zoom,
|
required this.zoom,
|
||||||
required this.groupKey,
|
required this.groupKey,
|
||||||
|
this.currentUserLocation,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1291,6 +1347,41 @@ class _DelayedMultiMarkerMapState extends State<_DelayedMultiMarkerMap> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final markers = <Marker>[
|
||||||
|
...widget.positions.map((pos) => Marker(
|
||||||
|
point: pos,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red.withOpacity(0.8),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 1.5)),
|
||||||
|
child: const Icon(Icons.train, color: Colors.white, size: 12)))),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (widget.currentUserLocation != null) {
|
||||||
|
markers.add(
|
||||||
|
Marker(
|
||||||
|
point: widget.currentUserLocation!,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 1.5),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.my_location,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return FlutterMap(
|
return FlutterMap(
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
onPositionChanged: (position, hasGesture) => _onCameraMove(),
|
onPositionChanged: (position, hasGesture) => _onCameraMove(),
|
||||||
@@ -1303,20 +1394,7 @@ class _DelayedMultiMarkerMapState extends State<_DelayedMultiMarkerMap> {
|
|||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
userAgentPackageName: 'org.noxylva.lbjconsole',
|
userAgentPackageName: 'org.noxylva.lbjconsole',
|
||||||
),
|
),
|
||||||
MarkerLayer(
|
MarkerLayer(markers: markers),
|
||||||
markers: widget.positions
|
|
||||||
.map((pos) => Marker(
|
|
||||||
point: pos,
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red.withOpacity(0.8),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: Colors.white, width: 2)),
|
|
||||||
child: const Icon(Icons.train,
|
|
||||||
color: Colors.white, size: 20))))
|
|
||||||
.toList()),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math' show sin, cos, sqrt, atan2, pi;
|
||||||
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';
|
||||||
@@ -29,13 +30,89 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
bool _isLocationPermissionGranted = false;
|
bool _isLocationPermissionGranted = false;
|
||||||
Timer? _locationTimer;
|
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),
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
print('=== 地图页面初始化 ===');
|
||||||
_initializeMap();
|
_initializeMap();
|
||||||
_loadTrainRecords();
|
|
||||||
_loadSettings();
|
_checkDatabaseSettings();
|
||||||
_startLocationUpdates();
|
|
||||||
|
//
|
||||||
|
_loadSettings().then((_) {
|
||||||
|
print('设置加载完成,开始加载列车记录');
|
||||||
|
_loadTrainRecords().then((_) {
|
||||||
|
print('列车记录加载完成,开始位置更新');
|
||||||
|
_startLocationUpdates();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkDatabaseSettings() async {
|
||||||
|
try {
|
||||||
|
print('=== 检查数据库设置 ===');
|
||||||
|
final dbInfo = await DatabaseService.instance.getDatabaseInfo();
|
||||||
|
print('数据库信息: $dbInfo');
|
||||||
|
|
||||||
|
final settings = await DatabaseService.instance.getAllSettings();
|
||||||
|
print('数据库设置详情: $settings');
|
||||||
|
|
||||||
|
if (settings != null) {
|
||||||
|
final lat = settings['mapCenterLat'];
|
||||||
|
final lon = settings['mapCenterLon'];
|
||||||
|
print('数据库中的位置坐标: lat=$lat, lon=$lon');
|
||||||
|
|
||||||
|
if (lat != null && lon != null) {
|
||||||
|
if (lat == 39.9042 && lon == 116.4074) {
|
||||||
|
print('警告:数据库中保存的是北京默认坐标');
|
||||||
|
} else if (lat == 0.0 && lon == 0.0) {
|
||||||
|
print('警告:数据库中保存的是零坐标');
|
||||||
|
} else {
|
||||||
|
print('数据库中保存的是有效坐标');
|
||||||
|
final beijingLat = 39.9042;
|
||||||
|
final beijingLon = 116.4074;
|
||||||
|
final distance =
|
||||||
|
_calculateDistance(lat, lon, beijingLat, beijingLon);
|
||||||
|
print('与北京市中心的距离: ${distance.toStringAsFixed(2)} 公里');
|
||||||
|
|
||||||
|
if (distance < 50) {
|
||||||
|
print('注意:保存的位置在北京附近(距离 < 50公里)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('检查数据库设置失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _calculateDistance(
|
||||||
|
double lat1, double lon1, double lat2, double lon2) {
|
||||||
|
const earthRadius = 6371;
|
||||||
|
final dLat = _degreesToRadians(lat2 - lat1);
|
||||||
|
final dLon = _degreesToRadians(lon2 - lon1);
|
||||||
|
final a = sin(dLat / 2) * sin(dLat / 2) +
|
||||||
|
cos(_degreesToRadians(lat1)) *
|
||||||
|
cos(_degreesToRadians(lat2)) *
|
||||||
|
sin(dLon / 2) *
|
||||||
|
sin(dLon / 2);
|
||||||
|
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||||
|
return earthRadius * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _degreesToRadians(double degrees) {
|
||||||
|
return degrees * pi / 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -77,15 +154,25 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
|
|
||||||
Future<void> _getCurrentLocation() async {
|
Future<void> _getCurrentLocation() async {
|
||||||
try {
|
try {
|
||||||
|
print('=== 获取当前位置 ===');
|
||||||
Position position = await Geolocator.getCurrentPosition(
|
Position position = await Geolocator.getCurrentPosition(
|
||||||
desiredAccuracy: LocationAccuracy.high,
|
desiredAccuracy: LocationAccuracy.high,
|
||||||
forceAndroidLocationManager: true,
|
forceAndroidLocationManager: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final newLocation = LatLng(position.latitude, position.longitude);
|
||||||
|
print('获取到位置: $newLocation');
|
||||||
setState(() {
|
setState(() {
|
||||||
_userLocation = LatLng(position.latitude, position.longitude);
|
_userLocation = newLocation;
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
|
||||||
|
if (!_isMapInitialized) {
|
||||||
|
print('获取位置后尝试初始化地图');
|
||||||
|
_initializeMapPosition();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('获取位置失败: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startLocationUpdates() {
|
void _startLocationUpdates() {
|
||||||
@@ -119,43 +206,83 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
|
|
||||||
Future<void> _loadSettings() async {
|
Future<void> _loadSettings() async {
|
||||||
try {
|
try {
|
||||||
|
print('=== 开始加载设置 ===');
|
||||||
final settings = await DatabaseService.instance.getAllSettings();
|
final settings = await DatabaseService.instance.getAllSettings();
|
||||||
|
print('设置数据: $settings');
|
||||||
if (settings != null) {
|
if (settings != null) {
|
||||||
|
print(
|
||||||
|
'设置中的位置: lat=${settings['mapCenterLat']}, lon=${settings['mapCenterLon']}');
|
||||||
|
print('设置中的缩放: ${settings['mapZoomLevel']}');
|
||||||
setState(() {
|
setState(() {
|
||||||
_railwayLayerVisible =
|
_railwayLayerVisible =
|
||||||
(settings['mapRailwayLayerVisible'] as int?) == 1;
|
(settings['mapRailwayLayerVisible'] as int?) == 1;
|
||||||
_currentZoom = (settings['mapZoomLevel'] as num?)?.toDouble() ?? 10.0;
|
_currentZoom = (settings['mapZoomLevel'] as num?)?.toDouble() ?? 10.0;
|
||||||
_currentRotation =
|
_currentRotation =
|
||||||
(settings['mapRotation'] as num?)?.toDouble() ?? 0.0;
|
(settings['mapRotation'] as num?)?.toDouble() ?? 0.0;
|
||||||
|
_selectedTimeFilter =
|
||||||
|
settings['mapTimeFilter'] as String? ?? 'unlimited';
|
||||||
|
|
||||||
final lat = (settings['mapCenterLat'] as num?)?.toDouble();
|
final lat = (settings['mapCenterLat'] as num?)?.toDouble();
|
||||||
final lon = (settings['mapCenterLon'] as num?)?.toDouble();
|
final lon = (settings['mapCenterLon'] as num?)?.toDouble();
|
||||||
|
|
||||||
if (lat != null && lon != null) {
|
if (lat != null && lon != null && lat != 0.0 && lon != 0.0) {
|
||||||
_currentLocation = LatLng(lat, lon);
|
_currentLocation = LatLng(lat, lon);
|
||||||
|
print('使用保存的位置: $_currentLocation');
|
||||||
|
} else {
|
||||||
|
print('保存的位置无效或为零,不使用');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
print('设置加载完成,当前位置: $_currentLocation');
|
||||||
|
if (!_isMapInitialized) {
|
||||||
|
print('设置加载后尝试初始化地图');
|
||||||
|
_initializeMapPosition();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('没有保存的设置数据');
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
print('加载设置失败: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveSettings() async {
|
Future<void> _saveSettings() async {
|
||||||
try {
|
try {
|
||||||
|
print('=== 保存设置到数据库 ===');
|
||||||
|
print('当前旋转角度: $_currentRotation');
|
||||||
|
print('当前缩放级别: $_currentZoom');
|
||||||
|
print('当前位置: $_currentLocation');
|
||||||
|
|
||||||
final center = _mapController.camera.center;
|
final center = _mapController.camera.center;
|
||||||
await DatabaseService.instance.updateSettings({
|
|
||||||
|
final isDefaultLocation =
|
||||||
|
center.latitude == 39.9042 && center.longitude == 116.4074;
|
||||||
|
|
||||||
|
final settings = {
|
||||||
'mapRailwayLayerVisible': _railwayLayerVisible ? 1 : 0,
|
'mapRailwayLayerVisible': _railwayLayerVisible ? 1 : 0,
|
||||||
'mapZoomLevel': _currentZoom,
|
'mapZoomLevel': _currentZoom,
|
||||||
'mapCenterLat': center.latitude,
|
|
||||||
'mapCenterLon': center.longitude,
|
|
||||||
'mapRotation': _currentRotation,
|
'mapRotation': _currentRotation,
|
||||||
});
|
'mapTimeFilter': _selectedTimeFilter,
|
||||||
} catch (e) {}
|
};
|
||||||
|
|
||||||
|
if (!isDefaultLocation) {
|
||||||
|
settings['mapCenterLat'] = center.latitude;
|
||||||
|
settings['mapCenterLon'] = center.longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
print('保存的设置数据: $settings');
|
||||||
|
await DatabaseService.instance.updateSettings(settings);
|
||||||
|
print('=== 设置保存成功 ===');
|
||||||
|
} catch (e) {
|
||||||
|
print('保存设置失败: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadTrainRecords() async {
|
Future<void> _loadTrainRecords() async {
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
try {
|
try {
|
||||||
final records = await DatabaseService.instance.getAllRecords();
|
print('=== 开始加载列车记录 ===');
|
||||||
|
final records = await _getFilteredRecords();
|
||||||
|
print('加载到 ${records.length} 条记录');
|
||||||
setState(() {
|
setState(() {
|
||||||
_trainRecords.clear();
|
_trainRecords.clear();
|
||||||
_trainRecords.addAll(records);
|
_trainRecords.addAll(records);
|
||||||
@@ -163,20 +290,46 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
|
|
||||||
if (_trainRecords.isNotEmpty) {
|
if (_trainRecords.isNotEmpty) {
|
||||||
final lastRecord = _trainRecords.first;
|
final lastRecord = _trainRecords.first;
|
||||||
|
print(
|
||||||
|
'最新记录: ${lastRecord.fullTrainNumber}, 位置: ${lastRecord.position}');
|
||||||
final coords = lastRecord.getCoordinates();
|
final coords = lastRecord.getCoordinates();
|
||||||
final dmsCoords = _parseDmsCoordinate(lastRecord.positionInfo);
|
final dmsCoords = _parseDmsCoordinate(lastRecord.positionInfo);
|
||||||
|
|
||||||
if (dmsCoords != null) {
|
if (dmsCoords != null) {
|
||||||
_lastTrainLocation = dmsCoords;
|
_lastTrainLocation = dmsCoords;
|
||||||
|
print('使用DMS坐标: $dmsCoords');
|
||||||
} else if (coords['lat'] != 0.0 && coords['lng'] != 0.0) {
|
} else if (coords['lat'] != 0.0 && coords['lng'] != 0.0) {
|
||||||
_lastTrainLocation = LatLng(coords['lat']!, coords['lng']!);
|
_lastTrainLocation = LatLng(coords['lat']!, coords['lng']!);
|
||||||
|
print('使用解析坐标: $_lastTrainLocation');
|
||||||
|
} else {
|
||||||
|
print('记录中没有有效坐标');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
print('没有列车记录');
|
||||||
}
|
}
|
||||||
|
|
||||||
_initializeMapPosition();
|
print('列车位置: $_lastTrainLocation');
|
||||||
|
if (!_isMapInitialized) {
|
||||||
|
print('列车记录加载后尝试初始化地图');
|
||||||
|
_initializeMapPosition();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
|
print('加载列车记录失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TrainRecord>> _getFilteredRecords() async {
|
||||||
|
if (_selectedTimeFilter == 'unlimited') {
|
||||||
|
return await DatabaseService.instance.getAllRecords();
|
||||||
|
} else {
|
||||||
|
final duration = _timeFilterOptions[_selectedTimeFilter];
|
||||||
|
if (duration != null && duration != Duration.zero) {
|
||||||
|
return await DatabaseService.instance
|
||||||
|
.getRecordsWithinReceivedTimeRange(duration);
|
||||||
|
}
|
||||||
|
return await DatabaseService.instance.getAllRecords();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,23 +338,36 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
|
|
||||||
LatLng? targetLocation;
|
LatLng? targetLocation;
|
||||||
|
|
||||||
|
print('=== 初始化地图位置 ===');
|
||||||
|
print('当前位置: $_currentLocation');
|
||||||
|
print('列车位置: $_lastTrainLocation');
|
||||||
|
print('用户位置: $_userLocation');
|
||||||
|
print('地图已初始化: $_isMapInitialized');
|
||||||
|
|
||||||
if (_currentLocation != null) {
|
if (_currentLocation != null) {
|
||||||
targetLocation = _currentLocation;
|
targetLocation = _currentLocation;
|
||||||
} else if (_userLocation != null) {
|
print('使用保存的坐标: $targetLocation');
|
||||||
targetLocation = _userLocation;
|
|
||||||
} else if (_lastTrainLocation != null) {
|
} else if (_lastTrainLocation != null) {
|
||||||
targetLocation = _lastTrainLocation;
|
targetLocation = _lastTrainLocation;
|
||||||
|
print('使用列车位置: $targetLocation');
|
||||||
|
} else if (_userLocation != null) {
|
||||||
|
targetLocation = _userLocation;
|
||||||
|
print('使用用户位置: $targetLocation');
|
||||||
} else {
|
} else {
|
||||||
_isMapInitialized = true;
|
targetLocation = const LatLng(39.9042, 116.4074);
|
||||||
return;
|
print('没有可用位置,使用北京默认位置: $targetLocation');
|
||||||
}
|
}
|
||||||
|
|
||||||
_centerMap(targetLocation!, zoom: _currentZoom);
|
print('最终选择位置: $targetLocation');
|
||||||
|
print('当前旋转角度: $_currentRotation');
|
||||||
|
_centerMap(targetLocation!, zoom: _currentZoom, rotation: _currentRotation);
|
||||||
_isMapInitialized = true;
|
_isMapInitialized = true;
|
||||||
|
print('地图初始化完成,旋转角度: $_currentRotation');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _centerMap(LatLng location, {double? zoom}) {
|
void _centerMap(LatLng location, {double? zoom, double? rotation}) {
|
||||||
_mapController.move(location, zoom ?? _currentZoom);
|
_mapController.move(location, zoom ?? _currentZoom);
|
||||||
|
_mapController.rotate(rotation ?? _currentRotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
LatLng? _parseDmsCoordinate(String? positionInfo) {
|
LatLng? _parseDmsCoordinate(String? positionInfo) {
|
||||||
@@ -292,41 +458,27 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
Marker(
|
Marker(
|
||||||
point: position,
|
point: position,
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 60,
|
height: 16,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => position != null
|
onTap: () => position != null
|
||||||
? _showTrainDetailsDialog(record, position)
|
? _showTrainDetailsDialog(record, position)
|
||||||
: null,
|
: null,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red,
|
|
||||||
borderRadius: BorderRadius.circular(18),
|
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.train,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Container(
|
Container(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.7),
|
color: Colors.black.withOpacity(0.8),
|
||||||
borderRadius: BorderRadius.circular(2),
|
borderRadius: BorderRadius.circular(3),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
trainDisplay,
|
trainDisplay,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 10,
|
fontSize: 8,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -345,8 +497,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _centerToMyLocation() {
|
void _centerToMyLocation() {
|
||||||
_centerMap(_lastTrainLocation ?? const LatLng(39.9042, 116.4074),
|
if (_userLocation != null) {
|
||||||
zoom: 15.0);
|
_centerMap(_userLocation!, zoom: 15.0, rotation: _currentRotation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _centerToLastTrain() {
|
void _centerToLastTrain() {
|
||||||
@@ -363,11 +516,73 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetPosition != null) {
|
if (targetPosition != null) {
|
||||||
_centerMap(targetPosition, zoom: 15.0);
|
_centerMap(targetPosition, zoom: 15.0, rotation: _currentRotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showTimeFilterDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('时间筛选'),
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.minPositive,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: _timeFilterOptions.keys.map((key) {
|
||||||
|
return RadioListTile<String>(
|
||||||
|
title: Text(_getTimeFilterLabel(key)),
|
||||||
|
value: key,
|
||||||
|
groupValue: _selectedTimeFilter,
|
||||||
|
onChanged: (String? value) {
|
||||||
|
if (value != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedTimeFilter = value;
|
||||||
|
});
|
||||||
|
_loadTrainRecords();
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
dense: true,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('取消'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getTimeFilterLabel(String key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'unlimited':
|
||||||
|
return '全部时间';
|
||||||
|
case '1hour':
|
||||||
|
return '最近1小时';
|
||||||
|
case '6hours':
|
||||||
|
return '最近6小时';
|
||||||
|
case '12hours':
|
||||||
|
return '最近12小时';
|
||||||
|
case '24hours':
|
||||||
|
return '最近24小时';
|
||||||
|
case '7days':
|
||||||
|
return '最近7天';
|
||||||
|
case '30days':
|
||||||
|
return '最近30天';
|
||||||
|
default:
|
||||||
|
return '未知';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _showTrainDetailsDialog(TrainRecord record, LatLng position) {
|
void _showTrainDetailsDialog(TrainRecord record, LatLng position) {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -428,9 +643,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildMaterial3DetailRow(
|
_buildMaterial3DetailRow(
|
||||||
context, "时间", record.formattedTime),
|
context, "时间", _getDisplayTime(record)),
|
||||||
_buildMaterial3DetailRow(
|
_buildMaterial3DetailRow(
|
||||||
context, "日期", record.formattedDate),
|
context, "日期", _getDisplayDate(record)),
|
||||||
_buildMaterial3DetailRow(
|
_buildMaterial3DetailRow(
|
||||||
context, "类型", record.trainType),
|
context, "类型", record.trainType),
|
||||||
_buildMaterial3DetailRow(context, "速度",
|
_buildMaterial3DetailRow(context, "速度",
|
||||||
@@ -470,7 +685,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_centerMap(position, zoom: 17.0);
|
_centerMap(position,
|
||||||
|
zoom: 17.0, rotation: _currentRotation);
|
||||||
},
|
},
|
||||||
child: const Row(
|
child: const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -516,6 +732,25 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getDisplayTime(TrainRecord record) {
|
||||||
|
if (record.time == "<NUL>" || record.time.isEmpty) {
|
||||||
|
final receivedTime = record.receivedTimestamp;
|
||||||
|
return '${receivedTime.hour.toString().padLeft(2, '0')}:${receivedTime.minute.toString().padLeft(2, '0')}:${receivedTime.second.toString().padLeft(2, '0')}';
|
||||||
|
} else {
|
||||||
|
return record.time.split("\n")[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getDisplayDate(TrainRecord record) {
|
||||||
|
if (record.time == "<NUL>" || record.time.isEmpty) {
|
||||||
|
final receivedTime = record.receivedTimestamp;
|
||||||
|
return '${receivedTime.year}-${receivedTime.month.toString().padLeft(2, '0')}-${receivedTime.day.toString().padLeft(2, '0')}';
|
||||||
|
} else {
|
||||||
|
final now = DateTime.now();
|
||||||
|
return '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildMaterial3DetailRow(
|
Widget _buildMaterial3DetailRow(
|
||||||
BuildContext context, String label, String value) {
|
BuildContext context, String label, String value) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -555,18 +790,18 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
markers.add(
|
markers.add(
|
||||||
Marker(
|
Marker(
|
||||||
point: _userLocation!,
|
point: _userLocation!,
|
||||||
width: 40,
|
width: 24,
|
||||||
height: 40,
|
height: 24,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
shape: BoxShape.circle,
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
border: Border.all(color: Colors.white, width: 1),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.my_location,
|
Icons.my_location,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 20,
|
size: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -580,21 +815,22 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
FlutterMap(
|
FlutterMap(
|
||||||
mapController: _mapController,
|
mapController: _mapController,
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
initialCenter:
|
initialCenter: _currentLocation ??
|
||||||
_lastTrainLocation ?? const LatLng(39.9042, 116.4074),
|
_lastTrainLocation ??
|
||||||
|
_userLocation ??
|
||||||
|
const LatLng(39.9042, 116.4074),
|
||||||
initialZoom: _currentZoom,
|
initialZoom: _currentZoom,
|
||||||
initialRotation: _currentRotation,
|
initialRotation: _currentRotation,
|
||||||
minZoom: 4.0,
|
minZoom: 4.0,
|
||||||
maxZoom: 18.0,
|
maxZoom: 18.0,
|
||||||
onPositionChanged: (MapCamera camera, bool hasGesture) {
|
onPositionChanged: (MapCamera camera, bool hasGesture) {
|
||||||
if (hasGesture) {
|
setState(() {
|
||||||
setState(() {
|
_currentLocation = camera.center;
|
||||||
_currentLocation = camera.center;
|
_currentZoom = camera.zoom;
|
||||||
_currentZoom = camera.zoom;
|
_currentRotation = camera.rotation;
|
||||||
_currentRotation = camera.rotation;
|
});
|
||||||
});
|
|
||||||
_saveSettings();
|
_saveSettings();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
@@ -625,6 +861,13 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
top: 40,
|
top: 40,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
FloatingActionButton.small(
|
||||||
|
heroTag: 'timeFilter',
|
||||||
|
backgroundColor: const Color(0xFF1E1E1E),
|
||||||
|
onPressed: _showTimeFilterDialog,
|
||||||
|
child: const Icon(Icons.filter_list, color: Colors.white),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
FloatingActionButton.small(
|
FloatingActionButton.small(
|
||||||
heroTag: 'railwayLayer',
|
heroTag: 'railwayLayer',
|
||||||
backgroundColor: const Color(0xFF1E1E1E),
|
backgroundColor: const Color(0xFF1E1E1E),
|
||||||
|
|||||||
@@ -319,6 +319,10 @@ class BLEService {
|
|||||||
'${now.millisecondsSinceEpoch}_${Random().nextInt(9999)}';
|
'${now.millisecondsSinceEpoch}_${Random().nextInt(9999)}';
|
||||||
recordData['receivedTimestamp'] = now.millisecondsSinceEpoch;
|
recordData['receivedTimestamp'] = now.millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
if (!recordData.containsKey('timestamp')) {
|
||||||
|
recordData['timestamp'] = now.millisecondsSinceEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
_lastReceivedTime = now;
|
_lastReceivedTime = now;
|
||||||
_lastReceivedTimeController.add(_lastReceivedTime);
|
_lastReceivedTimeController.add(_lastReceivedTime);
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class DatabaseService {
|
|||||||
DatabaseService._internal();
|
DatabaseService._internal();
|
||||||
|
|
||||||
static const String _databaseName = 'train_database';
|
static const String _databaseName = 'train_database';
|
||||||
static const _databaseVersion = 2;
|
static const _databaseVersion = 4;
|
||||||
|
|
||||||
static const String trainRecordsTable = 'train_records';
|
static const String trainRecordsTable = 'train_records';
|
||||||
static const String appSettingsTable = 'app_settings';
|
static const String appSettingsTable = 'app_settings';
|
||||||
@@ -43,6 +43,17 @@ class DatabaseService {
|
|||||||
await db.execute(
|
await db.execute(
|
||||||
'ALTER TABLE $appSettingsTable ADD COLUMN hideTimeOnlyRecords INTEGER NOT NULL DEFAULT 0');
|
'ALTER TABLE $appSettingsTable ADD COLUMN hideTimeOnlyRecords INTEGER NOT NULL DEFAULT 0');
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 3) {
|
||||||
|
await db.execute(
|
||||||
|
'ALTER TABLE $appSettingsTable ADD COLUMN mapTimeFilter TEXT NOT NULL DEFAULT "unlimited"');
|
||||||
|
}
|
||||||
|
if (oldVersion < 4) {
|
||||||
|
try {
|
||||||
|
await db.execute(
|
||||||
|
'ALTER TABLE $appSettingsTable ADD COLUMN mapTimeFilter TEXT NOT NULL DEFAULT "unlimited"');
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreate(Database db, int version) async {
|
Future<void> _onCreate(Database db, int version) async {
|
||||||
@@ -89,7 +100,8 @@ class DatabaseService {
|
|||||||
mergeRecordsEnabled INTEGER NOT NULL DEFAULT 0,
|
mergeRecordsEnabled INTEGER NOT NULL DEFAULT 0,
|
||||||
hideTimeOnlyRecords INTEGER NOT NULL DEFAULT 0,
|
hideTimeOnlyRecords INTEGER NOT NULL DEFAULT 0,
|
||||||
groupBy TEXT NOT NULL DEFAULT 'trainAndLoco',
|
groupBy TEXT NOT NULL DEFAULT 'trainAndLoco',
|
||||||
timeWindow TEXT NOT NULL DEFAULT 'unlimited'
|
timeWindow TEXT NOT NULL DEFAULT 'unlimited',
|
||||||
|
mapTimeFilter TEXT NOT NULL DEFAULT 'unlimited'
|
||||||
)
|
)
|
||||||
''');
|
''');
|
||||||
|
|
||||||
@@ -114,6 +126,7 @@ class DatabaseService {
|
|||||||
'hideTimeOnlyRecords': 0,
|
'hideTimeOnlyRecords': 0,
|
||||||
'groupBy': 'trainAndLoco',
|
'groupBy': 'trainAndLoco',
|
||||||
'timeWindow': 'unlimited',
|
'timeWindow': 'unlimited',
|
||||||
|
'mapTimeFilter': 'unlimited',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +148,31 @@ class DatabaseService {
|
|||||||
return result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
|
return result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<TrainRecord>> getRecordsWithinTimeRange(Duration duration) async {
|
||||||
|
final db = await database;
|
||||||
|
final cutoffTime = DateTime.now().subtract(duration).millisecondsSinceEpoch;
|
||||||
|
final result = await db.query(
|
||||||
|
trainRecordsTable,
|
||||||
|
where: 'timestamp >= ?',
|
||||||
|
whereArgs: [cutoffTime],
|
||||||
|
orderBy: 'timestamp DESC',
|
||||||
|
);
|
||||||
|
return result.map((json) => TrainRecord.fromDatabaseJson(json)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
Future<int> deleteRecord(String uniqueId) async {
|
Future<int> deleteRecord(String uniqueId) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
return await db.delete(
|
return await db.delete(
|
||||||
|
|||||||
@@ -142,8 +142,29 @@ class MergeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (group.length >= 2) {
|
if (group.length >= 2) {
|
||||||
|
final firstRecord = group.first;
|
||||||
|
final train = firstRecord.train.trim();
|
||||||
|
final loco = firstRecord.loco.trim();
|
||||||
|
String uniqueGroupKey;
|
||||||
|
|
||||||
|
if (train.isNotEmpty &&
|
||||||
|
train != "<NUL>" &&
|
||||||
|
!train.contains("-----") &&
|
||||||
|
loco.isNotEmpty &&
|
||||||
|
loco != "<NUL>") {
|
||||||
|
uniqueGroupKey = "train_or_loco:${train}_$loco";
|
||||||
|
} else if (train.isNotEmpty &&
|
||||||
|
train != "<NUL>" &&
|
||||||
|
!train.contains("-----")) {
|
||||||
|
uniqueGroupKey = "train_or_loco:train:$train";
|
||||||
|
} else if (loco.isNotEmpty && loco != "<NUL>") {
|
||||||
|
uniqueGroupKey = "train_or_loco:loco:$loco";
|
||||||
|
} else {
|
||||||
|
uniqueGroupKey = "train_or_loco:group_${mergedRecords.length}";
|
||||||
|
}
|
||||||
|
|
||||||
mergedRecords.add(MergedTrainRecord(
|
mergedRecords.add(MergedTrainRecord(
|
||||||
groupKey: "train_or_loco_group",
|
groupKey: uniqueGroupKey,
|
||||||
records: group,
|
records: group,
|
||||||
latestRecord: group.first,
|
latestRecord: group.first,
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user