mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
fix: Prevent invalid map bounds #123
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
@@ -26,22 +28,34 @@ class CustomMapConfig {
|
||||
|
||||
bool get isCalibrated => metersPerPixel != null && metersPerPixel! > 0;
|
||||
|
||||
LatLngBounds get bounds => LatLngBounds(
|
||||
const LatLng(0, 0),
|
||||
LatLng(imageHeight.toDouble(), imageWidth.toDouble()),
|
||||
);
|
||||
double get _displayScale {
|
||||
final heightScale = imageHeight / 90.0;
|
||||
final widthScale = imageWidth / 180.0;
|
||||
return math.max(1.0, math.max(heightScale, widthScale));
|
||||
}
|
||||
|
||||
LatLngBounds get displayBounds => LatLngBounds(
|
||||
LatLng(imageHeight.toDouble(), 0),
|
||||
LatLng(0, imageWidth.toDouble()),
|
||||
);
|
||||
double get _displayHeight => imageHeight / _displayScale;
|
||||
|
||||
double get _displayWidth => imageWidth / _displayScale;
|
||||
|
||||
LatLngBounds get bounds =>
|
||||
LatLngBounds(const LatLng(0, 0), LatLng(_displayHeight, _displayWidth));
|
||||
|
||||
LatLngBounds get displayBounds =>
|
||||
LatLngBounds(LatLng(_displayHeight, 0), LatLng(0, _displayWidth));
|
||||
|
||||
LatLng toDisplayPoint(LatLng point) {
|
||||
return LatLng(imageHeight.toDouble() - point.latitude, point.longitude);
|
||||
return LatLng(
|
||||
_displayHeight - (point.latitude / _displayScale),
|
||||
point.longitude / _displayScale,
|
||||
);
|
||||
}
|
||||
|
||||
LatLng fromDisplayPoint(LatLng point) {
|
||||
return LatLng(imageHeight.toDouble() - point.latitude, point.longitude);
|
||||
return LatLng(
|
||||
(_displayHeight - point.latitude) * _displayScale,
|
||||
point.longitude * _displayScale,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
||||
@@ -735,7 +735,10 @@ class MessagesProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
if (message.isContactMessage) {
|
||||
return existing.senderKeyShort == message.senderKeyShort;
|
||||
// Match by sender key + sender timestamp (matches official app's DB
|
||||
// uniqueness: contactPublicKey + senderTimestamp + text + txtType).
|
||||
return existing.senderKeyShort == message.senderKeyShort &&
|
||||
existing.senderTimestamp == message.senderTimestamp;
|
||||
}
|
||||
|
||||
if (message.isChannelMessage) {
|
||||
|
||||
@@ -169,8 +169,13 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
});
|
||||
|
||||
try {
|
||||
final contactsProvider = context.read<ContactsProvider>();
|
||||
for (final advert in adverts) {
|
||||
if (!mounted) break;
|
||||
// Skip already-resolved contacts
|
||||
if (contactsProvider.findContactByKey(advert.publicKey) != null) {
|
||||
continue;
|
||||
}
|
||||
await _resolveAdvert(advert);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -980,7 +980,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
builder: (context) => SimpleDialog(
|
||||
title: const Text('Route path byte size'),
|
||||
children: [
|
||||
for (final value in [1, 2, 3])
|
||||
for (final value in RouteHashPreferences.supportedSizes)
|
||||
SimpleDialogOption(
|
||||
onPressed: () => Navigator.of(context).pop(value),
|
||||
child: Row(
|
||||
@@ -990,7 +990,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('$value byte${value == 1 ? '' : 's'}'),
|
||||
Text('$value-byte (max ${64 ~/ value} hops)'),
|
||||
Text(
|
||||
'Use ${value * 2} hex characters per hop',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
|
||||
@@ -20,6 +20,14 @@ class MeshMapNode {
|
||||
required this.updatedAtMs,
|
||||
});
|
||||
|
||||
bool get hasValidCoordinates =>
|
||||
latitude >= -90 &&
|
||||
latitude <= 90 &&
|
||||
longitude >= -180 &&
|
||||
longitude <= 180 &&
|
||||
latitude != 0 &&
|
||||
longitude != 0;
|
||||
|
||||
factory MeshMapNode.fromJson(Map<String, dynamic> json) {
|
||||
return MeshMapNode(
|
||||
type: (json['type'] as num?)?.toInt() ?? 0,
|
||||
@@ -77,9 +85,7 @@ class MeshMapNodesService {
|
||||
final nodes = nodesRaw
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(MeshMapNode.fromJson)
|
||||
.where(
|
||||
(n) => n.publicKey.isNotEmpty && n.latitude != 0 && n.longitude != 0,
|
||||
)
|
||||
.where((n) => n.publicKey.isNotEmpty && n.hasValidCoordinates)
|
||||
.toList();
|
||||
|
||||
await _storeCache(nodes, cachedAt: now);
|
||||
@@ -116,9 +122,7 @@ class MeshMapNodesService {
|
||||
final nodes = decoded
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(MeshMapNode.fromJson)
|
||||
.where(
|
||||
(n) => n.publicKey.isNotEmpty && n.latitude != 0 && n.longitude != 0,
|
||||
)
|
||||
.where((n) => n.publicKey.isNotEmpty && n.hasValidCoordinates)
|
||||
.toList();
|
||||
_cachedNodes = nodes;
|
||||
_cachedAt = cachedAt;
|
||||
|
||||
@@ -23,10 +23,13 @@ class RouteHashPreferences {
|
||||
|
||||
static int normalizeSync(int value) => _normalize(value);
|
||||
|
||||
/// Valid hash sizes per the MeshCore protocol.
|
||||
/// The path encoding uses 2 bits: 00=1B, 01=2B, 10=3B, 11=4B.
|
||||
/// The official app offers 1, 2, 4 (skipping 3).
|
||||
static const List<int> supportedSizes = [1, 2, 4];
|
||||
|
||||
static int _normalize(int value) {
|
||||
if (value < 1 || value > 3) {
|
||||
return defaultHashSize;
|
||||
}
|
||||
return value;
|
||||
if (supportedSizes.contains(value)) return value;
|
||||
return defaultHashSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ class _ContactTraceSheetState extends State<ContactTraceSheet> {
|
||||
final concreteNodes = routeEntries
|
||||
.where((entry) => entry.resolved.node != null)
|
||||
.map((entry) => entry.resolved.node!)
|
||||
.where((node) => node.hasValidCoordinates)
|
||||
.toList();
|
||||
final mapPoints = concreteNodes
|
||||
.map((node) => LatLng(node.latitude, node.longitude))
|
||||
@@ -395,6 +396,7 @@ class _ContactTraceSheetState extends State<ContactTraceSheet> {
|
||||
);
|
||||
})
|
||||
.whereType<MeshMapNode>()
|
||||
.where((node) => node.hasValidCoordinates)
|
||||
.toList();
|
||||
|
||||
final selfNode = _selfNode(connectionProvider);
|
||||
@@ -415,7 +417,7 @@ class _ContactTraceSheetState extends State<ContactTraceSheet> {
|
||||
return null;
|
||||
}
|
||||
|
||||
return MeshMapNode(
|
||||
final node = MeshMapNode(
|
||||
type: -1,
|
||||
name: connectionProvider.deviceInfo.selfName?.trim().isNotEmpty == true
|
||||
? connectionProvider.deviceInfo.selfName!.trim()
|
||||
@@ -428,6 +430,7 @@ class _ContactTraceSheetState extends State<ContactTraceSheet> {
|
||||
longitude: advLon / 1e6,
|
||||
updatedAtMs: DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
return node.hasValidCoordinates ? node : null;
|
||||
}
|
||||
|
||||
List<MeshMapNode> _mergeNodes(
|
||||
|
||||
@@ -144,6 +144,7 @@ class _MessageTraceSheetState extends State<MessageTraceSheet> {
|
||||
final concretePathNodes = routeEntries
|
||||
.where((entry) => entry.resolved.node != null)
|
||||
.map((entry) => entry.resolved.node!)
|
||||
.where((node) => node.hasValidCoordinates)
|
||||
.toList();
|
||||
final mapPoints = concretePathNodes
|
||||
.map((n) => LatLng(n.latitude, n.longitude))
|
||||
@@ -390,6 +391,7 @@ class _MessageTraceSheetState extends State<MessageTraceSheet> {
|
||||
);
|
||||
})
|
||||
.whereType<MeshMapNode>()
|
||||
.where((node) => node.hasValidCoordinates)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user