fix: Prevent invalid map bounds #123

This commit is contained in:
Janez T
2026-03-16 22:16:36 +01:00
parent cfd233d38e
commit 993563f059
11 changed files with 129 additions and 25 deletions

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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(

View File

@@ -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();
}