mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
use real node id for pending uploads, more camera -> node
This commit is contained in:
@@ -88,11 +88,8 @@ flutter run
|
||||
## Roadmap
|
||||
|
||||
### v1 todo/bug List
|
||||
- Fix "tiles loaded" indicator accuracy across different providers
|
||||
- Generic tile provider error messages (not always "OSM tiles slow")
|
||||
- Optional custom icons for camera profiles
|
||||
- Camera deletions
|
||||
- Clean up cache when submitted changesets appear in Overpass results
|
||||
- Upgrade device marker design (considering nullplate's svg)
|
||||
|
||||
### Future Features & Wishlist
|
||||
|
||||
@@ -34,7 +34,7 @@ const String kClientName = 'DeFlock';
|
||||
const String kClientVersion = '0.9.11';
|
||||
|
||||
// Development/testing features - set to false for production builds
|
||||
const bool kEnableDevelopmentModes = false; // Set to false to hide sandbox/simulate modes and force production mode
|
||||
const bool kEnableDevelopmentModes = true; // Set to false to hide sandbox/simulate modes and force production mode
|
||||
|
||||
// Marker/node interaction
|
||||
const int kCameraMinZoomLevel = 10; // Minimum zoom to show nodes or warning
|
||||
|
||||
@@ -10,6 +10,7 @@ class PendingUpload {
|
||||
final OperatorProfile? operatorProfile;
|
||||
final UploadMode uploadMode; // Capture upload destination when queued
|
||||
final int? originalNodeId; // If this is an edit, the ID of the original OSM node
|
||||
int? submittedNodeId; // The actual node ID returned by OSM after successful submission
|
||||
int attempts;
|
||||
bool error;
|
||||
bool completing; // True when upload succeeded but item is showing checkmark briefly
|
||||
@@ -21,12 +22,13 @@ class PendingUpload {
|
||||
this.operatorProfile,
|
||||
required this.uploadMode,
|
||||
this.originalNodeId,
|
||||
this.submittedNodeId,
|
||||
this.attempts = 0,
|
||||
this.error = false,
|
||||
this.completing = false,
|
||||
});
|
||||
|
||||
// True if this is an edit of an existing camera, false if it's a new camera
|
||||
// True if this is an edit of an existing node, false if it's a new node
|
||||
bool get isEdit => originalNodeId != null;
|
||||
|
||||
// Get display name for the upload destination
|
||||
@@ -41,11 +43,11 @@ class PendingUpload {
|
||||
}
|
||||
}
|
||||
|
||||
// Get combined tags from camera profile and operator profile
|
||||
// Get combined tags from node profile and operator profile
|
||||
Map<String, String> getCombinedTags() {
|
||||
final tags = Map<String, String>.from(profile.tags);
|
||||
|
||||
// Add operator profile tags (they override camera profile tags if there are conflicts)
|
||||
// Add operator profile tags (they override node profile tags if there are conflicts)
|
||||
if (operatorProfile != null) {
|
||||
tags.addAll(operatorProfile!.tags);
|
||||
}
|
||||
@@ -66,6 +68,7 @@ class PendingUpload {
|
||||
'operatorProfile': operatorProfile?.toJson(),
|
||||
'uploadMode': uploadMode.index,
|
||||
'originalNodeId': originalNodeId,
|
||||
'submittedNodeId': submittedNodeId,
|
||||
'attempts': attempts,
|
||||
'error': error,
|
||||
'completing': completing,
|
||||
@@ -84,6 +87,7 @@ class PendingUpload {
|
||||
? UploadMode.values[j['uploadMode']]
|
||||
: UploadMode.production, // Default for legacy entries
|
||||
originalNodeId: j['originalNodeId'],
|
||||
submittedNodeId: j['submittedNodeId'],
|
||||
attempts: j['attempts'] ?? 0,
|
||||
error: j['error'] ?? false,
|
||||
completing: j['completing'] ?? false, // Default to false for legacy entries
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../models/osm_camera_node.dart';
|
||||
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
||||
|
||||
class CameraCache {
|
||||
// Singleton instance
|
||||
static final CameraCache instance = CameraCache._internal();
|
||||
factory CameraCache() => instance;
|
||||
CameraCache._internal();
|
||||
|
||||
final Map<int, OsmCameraNode> _nodes = {};
|
||||
|
||||
/// Add or update a batch of camera nodes in the cache.
|
||||
void addOrUpdate(List<OsmCameraNode> nodes) {
|
||||
for (var node in nodes) {
|
||||
final existing = _nodes[node.id];
|
||||
if (existing != null) {
|
||||
// Preserve any tags starting with underscore when updating existing nodes
|
||||
final mergedTags = Map<String, String>.from(node.tags);
|
||||
for (final entry in existing.tags.entries) {
|
||||
if (entry.key.startsWith('_')) {
|
||||
mergedTags[entry.key] = entry.value;
|
||||
}
|
||||
}
|
||||
_nodes[node.id] = OsmCameraNode(
|
||||
id: node.id,
|
||||
coord: node.coord,
|
||||
tags: mergedTags,
|
||||
);
|
||||
} else {
|
||||
_nodes[node.id] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Query for all cached cameras currently within the given LatLngBounds.
|
||||
List<OsmCameraNode> queryByBounds(LatLngBounds bounds) {
|
||||
return _nodes.values
|
||||
.where((node) => _inBounds(node.coord, bounds))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Retrieve all cached cameras.
|
||||
List<OsmCameraNode> getAll() => _nodes.values.toList();
|
||||
|
||||
/// Optionally clear the cache (rarely needed)
|
||||
void clear() => _nodes.clear();
|
||||
|
||||
/// Utility: point-in-bounds for coordinates
|
||||
bool _inBounds(LatLng coord, LatLngBounds bounds) {
|
||||
return coord.latitude >= bounds.southWest.latitude &&
|
||||
coord.latitude <= bounds.northEast.latitude &&
|
||||
coord.longitude >= bounds.southWest.longitude &&
|
||||
coord.longitude <= bounds.northEast.longitude;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_map/flutter_map.dart';
|
||||
|
||||
import '../../models/node_profile.dart';
|
||||
import '../../models/osm_camera_node.dart';
|
||||
import '../../models/pending_upload.dart';
|
||||
import '../../app_state.dart';
|
||||
import '../network_status.dart';
|
||||
|
||||
@@ -47,7 +48,7 @@ Future<List<OsmCameraNode>> fetchOverpassNodes({
|
||||
|
||||
NetworkStatus.instance.reportOverpassSuccess();
|
||||
|
||||
return elements.whereType<Map<String, dynamic>>().map((element) {
|
||||
final nodes = elements.whereType<Map<String, dynamic>>().map((element) {
|
||||
return OsmCameraNode(
|
||||
id: element['id'],
|
||||
coord: LatLng(element['lat'], element['lon']),
|
||||
@@ -55,6 +56,11 @@ Future<List<OsmCameraNode>> fetchOverpassNodes({
|
||||
);
|
||||
}).toList();
|
||||
|
||||
// Clean up any pending uploads that now appear in Overpass results
|
||||
_cleanupCompletedUploads(nodes);
|
||||
|
||||
return nodes;
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('[fetchOverpassNodes] Exception: $e');
|
||||
|
||||
@@ -92,4 +98,40 @@ String _buildOverpassQuery(LatLngBounds bounds, List<NodeProfile> profiles, int
|
||||
);
|
||||
$outputClause
|
||||
''';
|
||||
}
|
||||
|
||||
/// Clean up pending uploads that now appear in Overpass results
|
||||
void _cleanupCompletedUploads(List<OsmCameraNode> overpassNodes) {
|
||||
try {
|
||||
final appState = AppState.instance;
|
||||
final pendingUploads = appState.pendingUploads;
|
||||
|
||||
if (pendingUploads.isEmpty) return;
|
||||
|
||||
final overpassNodeIds = overpassNodes.map((n) => n.id).toSet();
|
||||
|
||||
// Find pending uploads whose submitted node IDs now appear in Overpass results
|
||||
final uploadsToRemove = <PendingUpload>[];
|
||||
|
||||
for (final upload in pendingUploads) {
|
||||
if (upload.submittedNodeId != null &&
|
||||
overpassNodeIds.contains(upload.submittedNodeId!)) {
|
||||
uploadsToRemove.add(upload);
|
||||
debugPrint('[OverpassCleanup] Found submitted node ${upload.submittedNodeId} in Overpass results, removing from pending queue');
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the completed uploads from the queue
|
||||
for (final upload in uploadsToRemove) {
|
||||
appState.removeFromQueue(upload);
|
||||
}
|
||||
|
||||
if (uploadsToRemove.isNotEmpty) {
|
||||
debugPrint('[OverpassCleanup] Cleaned up ${uploadsToRemove.length} completed uploads');
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('[OverpassCleanup] Error during cleanup: $e');
|
||||
// Don't let cleanup errors break the main functionality
|
||||
}
|
||||
}
|
||||
103
lib/services/node_cache.dart
Normal file
103
lib/services/node_cache.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../models/osm_camera_node.dart';
|
||||
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
||||
|
||||
class NodeCache {
|
||||
// Singleton instance
|
||||
static final NodeCache instance = NodeCache._internal();
|
||||
factory NodeCache() => instance;
|
||||
NodeCache._internal();
|
||||
|
||||
final Map<int, OsmCameraNode> _nodes = {};
|
||||
|
||||
/// Add or update a batch of nodes in the cache.
|
||||
void addOrUpdate(List<OsmCameraNode> nodes) {
|
||||
for (var node in nodes) {
|
||||
final existing = _nodes[node.id];
|
||||
if (existing != null) {
|
||||
// Preserve any tags starting with underscore when updating existing nodes
|
||||
final mergedTags = Map<String, String>.from(node.tags);
|
||||
for (final entry in existing.tags.entries) {
|
||||
if (entry.key.startsWith('_')) {
|
||||
mergedTags[entry.key] = entry.value;
|
||||
}
|
||||
}
|
||||
_nodes[node.id] = OsmCameraNode(
|
||||
id: node.id,
|
||||
coord: node.coord,
|
||||
tags: mergedTags,
|
||||
);
|
||||
} else {
|
||||
_nodes[node.id] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Query for all cached nodes currently within the given LatLngBounds.
|
||||
List<OsmCameraNode> queryByBounds(LatLngBounds bounds) {
|
||||
return _nodes.values
|
||||
.where((node) => _inBounds(node.coord, bounds))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Retrieve all cached nodes.
|
||||
List<OsmCameraNode> getAll() => _nodes.values.toList();
|
||||
|
||||
/// Optionally clear the cache (rarely needed)
|
||||
void clear() => _nodes.clear();
|
||||
|
||||
/// Remove the _pending_edit marker from a specific node
|
||||
void removePendingEditMarker(int nodeId) {
|
||||
final node = _nodes[nodeId];
|
||||
if (node != null && node.tags.containsKey('_pending_edit')) {
|
||||
final cleanTags = Map<String, String>.from(node.tags);
|
||||
cleanTags.remove('_pending_edit');
|
||||
|
||||
_nodes[nodeId] = OsmCameraNode(
|
||||
id: node.id,
|
||||
coord: node.coord,
|
||||
tags: cleanTags,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove temporary nodes (negative IDs) with _pending_upload marker at the given coordinate
|
||||
/// This is used when a real node ID is assigned to clean up temp placeholders
|
||||
void removeTempNodesByCoordinate(LatLng coord, {double tolerance = 0.00001}) {
|
||||
final nodesToRemove = <int>[];
|
||||
|
||||
for (final entry in _nodes.entries) {
|
||||
final nodeId = entry.key;
|
||||
final node = entry.value;
|
||||
|
||||
// Only consider temp nodes (negative IDs) with pending upload marker
|
||||
if (nodeId < 0 &&
|
||||
node.tags.containsKey('_pending_upload') &&
|
||||
_coordsMatch(node.coord, coord, tolerance)) {
|
||||
nodesToRemove.add(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
for (final nodeId in nodesToRemove) {
|
||||
_nodes.remove(nodeId);
|
||||
}
|
||||
|
||||
if (nodesToRemove.isNotEmpty) {
|
||||
print('[NodeCache] Removed ${nodesToRemove.length} temp nodes at coordinate ${coord.latitude}, ${coord.longitude}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if two coordinates match within tolerance
|
||||
bool _coordsMatch(LatLng coord1, LatLng coord2, double tolerance) {
|
||||
return (coord1.latitude - coord2.latitude).abs() < tolerance &&
|
||||
(coord1.longitude - coord2.longitude).abs() < tolerance;
|
||||
}
|
||||
|
||||
/// Utility: point-in-bounds for coordinates
|
||||
bool _inBounds(LatLng coord, LatLngBounds bounds) {
|
||||
return coord.latitude >= bounds.southWest.latitude &&
|
||||
coord.latitude <= bounds.northEast.latitude &&
|
||||
coord.longitude >= bounds.southWest.longitude &&
|
||||
coord.longitude <= bounds.northEast.longitude;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ class Uploader {
|
||||
Uploader(this.accessToken, this.onSuccess, {this.uploadMode = UploadMode.production});
|
||||
|
||||
final String accessToken;
|
||||
final void Function() onSuccess;
|
||||
final void Function(int nodeId) onSuccess;
|
||||
final UploadMode uploadMode;
|
||||
|
||||
Future<bool> upload(PendingUpload p) async {
|
||||
@@ -99,7 +99,8 @@ class Uploader {
|
||||
print('Uploader: Close response: ${closeResp.statusCode}');
|
||||
|
||||
print('Uploader: Upload successful!');
|
||||
onSuccess();
|
||||
final nodeIdInt = int.parse(nodeId);
|
||||
onSuccess(nodeIdInt);
|
||||
return true;
|
||||
} catch (e) {
|
||||
print('Uploader: Upload failed with error: $e');
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../models/pending_upload.dart';
|
||||
import '../models/osm_camera_node.dart';
|
||||
import '../services/camera_cache.dart';
|
||||
import '../services/node_cache.dart';
|
||||
import '../services/uploader.dart';
|
||||
import '../widgets/camera_provider_with_cache.dart';
|
||||
import 'settings_state.dart';
|
||||
@@ -37,7 +37,7 @@ class UploadQueueState extends ChangeNotifier {
|
||||
_queue.add(upload);
|
||||
_saveQueue();
|
||||
|
||||
// Add to camera cache immediately so it shows on the map
|
||||
// Add to node cache immediately so it shows on the map
|
||||
// Create a temporary node with a negative ID (to distinguish from real OSM nodes)
|
||||
// Using timestamp as negative ID to ensure uniqueness
|
||||
final tempId = -DateTime.now().millisecondsSinceEpoch;
|
||||
@@ -50,8 +50,8 @@ class UploadQueueState extends ChangeNotifier {
|
||||
tags: tags,
|
||||
);
|
||||
|
||||
CameraCache.instance.addOrUpdate([tempNode]);
|
||||
// Notify camera provider to update the map
|
||||
NodeCache.instance.addOrUpdate([tempNode]);
|
||||
// Notify node provider to update the map
|
||||
CameraProviderWithCache.instance.notifyListeners();
|
||||
|
||||
notifyListeners();
|
||||
@@ -73,7 +73,7 @@ class UploadQueueState extends ChangeNotifier {
|
||||
|
||||
// Create two cache entries:
|
||||
|
||||
// 1. Mark the original camera with _pending_edit (grey ring) at original location
|
||||
// 1. Mark the original node with _pending_edit (grey ring) at original location
|
||||
final originalTags = Map<String, String>.from(session.originalNode.tags);
|
||||
originalTags['_pending_edit'] = 'true'; // Mark original as having pending edit
|
||||
|
||||
@@ -83,7 +83,7 @@ class UploadQueueState extends ChangeNotifier {
|
||||
tags: originalTags,
|
||||
);
|
||||
|
||||
// 2. Create new temp node for the edited camera (purple ring) at new location
|
||||
// 2. Create new temp node for the edited node (purple ring) at new location
|
||||
final tempId = -DateTime.now().millisecondsSinceEpoch;
|
||||
final editedTags = upload.getCombinedTags();
|
||||
editedTags['_pending_upload'] = 'true'; // Mark as pending upload
|
||||
@@ -95,8 +95,8 @@ class UploadQueueState extends ChangeNotifier {
|
||||
tags: editedTags,
|
||||
);
|
||||
|
||||
CameraCache.instance.addOrUpdate([originalNode, editedNode]);
|
||||
// Notify camera provider to update the map
|
||||
NodeCache.instance.addOrUpdate([originalNode, editedNode]);
|
||||
// Notify node provider to update the map
|
||||
CameraProviderWithCache.instance.notifyListeners();
|
||||
|
||||
notifyListeners();
|
||||
@@ -153,19 +153,16 @@ class UploadQueueState extends ChangeNotifier {
|
||||
debugPrint('[UploadQueue] Simulating upload (no real API call)');
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate network delay
|
||||
ok = true;
|
||||
// Simulate a node ID for simulate mode
|
||||
_markAsCompleting(item, simulatedNodeId: DateTime.now().millisecondsSinceEpoch);
|
||||
} else {
|
||||
// Real upload -- use the upload mode that was saved when this item was queued
|
||||
debugPrint('[UploadQueue] Real upload to: ${item.uploadMode}');
|
||||
final up = Uploader(access, () {
|
||||
_markAsCompleting(item);
|
||||
final up = Uploader(access, (nodeId) {
|
||||
_markAsCompleting(item, submittedNodeId: nodeId);
|
||||
}, uploadMode: item.uploadMode);
|
||||
ok = await up.upload(item);
|
||||
}
|
||||
|
||||
if (ok && item.uploadMode == UploadMode.simulate) {
|
||||
// Mark as completing for simulate mode too
|
||||
_markAsCompleting(item);
|
||||
}
|
||||
if (!ok) {
|
||||
item.attempts++;
|
||||
if (item.attempts >= 3) {
|
||||
@@ -186,8 +183,22 @@ class UploadQueueState extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Mark an item as completing (shows checkmark) and schedule removal after 1 second
|
||||
void _markAsCompleting(PendingUpload item) {
|
||||
void _markAsCompleting(PendingUpload item, {int? submittedNodeId, int? simulatedNodeId}) {
|
||||
item.completing = true;
|
||||
|
||||
// Store the submitted node ID for cleanup purposes
|
||||
if (submittedNodeId != null) {
|
||||
item.submittedNodeId = submittedNodeId;
|
||||
debugPrint('[UploadQueue] Upload successful, OSM assigned node ID: $submittedNodeId');
|
||||
|
||||
// Update cache with real node ID instead of temp ID
|
||||
_updateCacheWithRealNodeId(item, submittedNodeId);
|
||||
} else if (simulatedNodeId != null && item.uploadMode == UploadMode.simulate) {
|
||||
// For simulate mode, use a fake but positive ID
|
||||
item.submittedNodeId = simulatedNodeId;
|
||||
debugPrint('[UploadQueue] Simulated upload, fake node ID: $simulatedNodeId');
|
||||
}
|
||||
|
||||
_saveQueue();
|
||||
notifyListeners();
|
||||
|
||||
@@ -198,6 +209,34 @@ class UploadQueueState extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
// Update the cache to use the real OSM node ID instead of temporary ID
|
||||
void _updateCacheWithRealNodeId(PendingUpload item, int realNodeId) {
|
||||
// Create the node with real ID and clean tags (remove temp markers)
|
||||
final tags = item.getCombinedTags();
|
||||
|
||||
final realNode = OsmCameraNode(
|
||||
id: realNodeId,
|
||||
coord: item.coord,
|
||||
tags: tags, // Clean tags without _pending_upload markers
|
||||
);
|
||||
|
||||
// Add/update the cache with the real node
|
||||
NodeCache.instance.addOrUpdate([realNode]);
|
||||
|
||||
// Clean up any temp nodes at the same coordinate
|
||||
NodeCache.instance.removeTempNodesByCoordinate(item.coord);
|
||||
|
||||
// For edits, also clean up the original node's _pending_edit marker
|
||||
if (item.isEdit && item.originalNodeId != null) {
|
||||
// Remove the _pending_edit marker from the original node in cache
|
||||
// The next Overpass fetch will provide the authoritative data anyway
|
||||
NodeCache.instance.removePendingEditMarker(item.originalNodeId!);
|
||||
}
|
||||
|
||||
// Notify node provider to update the map
|
||||
CameraProviderWithCache.instance.notifyListeners();
|
||||
}
|
||||
|
||||
// ---------- Queue persistence ----------
|
||||
Future<void> _saveQueue() async {
|
||||
|
||||
@@ -4,13 +4,13 @@ import 'package:latlong2/latlong.dart';
|
||||
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
||||
|
||||
import '../services/map_data_provider.dart';
|
||||
import '../services/camera_cache.dart';
|
||||
import '../services/node_cache.dart';
|
||||
import '../services/network_status.dart';
|
||||
import '../models/node_profile.dart';
|
||||
import '../models/osm_camera_node.dart';
|
||||
import '../app_state.dart';
|
||||
|
||||
/// Provides cameras for a map view, using an in-memory cache and optionally
|
||||
/// Provides surveillance nodes for a map view, using an in-memory cache and optionally
|
||||
/// merging in new results from Overpass via MapDataProvider when not offline.
|
||||
class CameraProviderWithCache extends ChangeNotifier {
|
||||
static final CameraProviderWithCache instance = CameraProviderWithCache._internal();
|
||||
@@ -21,16 +21,16 @@ class CameraProviderWithCache extends ChangeNotifier {
|
||||
|
||||
/// Call this to get (quickly) all cached overlays for the given view.
|
||||
/// Filters by currently enabled profiles.
|
||||
List<OsmCameraNode> getCachedCamerasForBounds(LatLngBounds bounds) {
|
||||
final allCameras = CameraCache.instance.queryByBounds(bounds);
|
||||
List<OsmCameraNode> getCachedNodesForBounds(LatLngBounds bounds) {
|
||||
final allNodes = NodeCache.instance.queryByBounds(bounds);
|
||||
final enabledProfiles = AppState.instance.enabledProfiles;
|
||||
|
||||
// If no profiles are enabled, show no cameras
|
||||
// If no profiles are enabled, show no nodes
|
||||
if (enabledProfiles.isEmpty) return [];
|
||||
|
||||
// Filter cameras to only show those matching enabled profiles
|
||||
return allCameras.where((camera) {
|
||||
return _matchesAnyProfile(camera, enabledProfiles);
|
||||
// Filter nodes to only show those matching enabled profiles
|
||||
return allNodes.where((node) {
|
||||
return _matchesAnyProfile(node, enabledProfiles);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ class CameraProviderWithCache extends ChangeNotifier {
|
||||
source: MapSource.auto,
|
||||
);
|
||||
if (fresh.isNotEmpty) {
|
||||
CameraCache.instance.addOrUpdate(fresh);
|
||||
// Clear waiting status when camera data arrives
|
||||
NodeCache.instance.addOrUpdate(fresh);
|
||||
// Clear waiting status when node data arrives
|
||||
NetworkStatus.instance.clearWaiting();
|
||||
notifyListeners();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[CameraProviderWithCache] Camera fetch failed: $e');
|
||||
debugPrint('[CameraProviderWithCache] Node fetch failed: $e');
|
||||
// Cache already holds whatever is available for the view
|
||||
}
|
||||
});
|
||||
@@ -69,7 +69,7 @@ class CameraProviderWithCache extends ChangeNotifier {
|
||||
|
||||
/// Optionally: clear the cache (could be used for testing/dev)
|
||||
void clearCache() {
|
||||
CameraCache.instance.clear();
|
||||
NodeCache.instance.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -78,18 +78,18 @@ class CameraProviderWithCache extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Check if a camera matches any of the provided profiles
|
||||
bool _matchesAnyProfile(OsmCameraNode camera, List<NodeProfile> profiles) {
|
||||
/// Check if a node matches any of the provided profiles
|
||||
bool _matchesAnyProfile(OsmCameraNode node, List<NodeProfile> profiles) {
|
||||
for (final profile in profiles) {
|
||||
if (_cameraMatchesProfile(camera, profile)) return true;
|
||||
if (_nodeMatchesProfile(node, profile)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check if a camera matches a specific profile (all profile tags must match)
|
||||
bool _cameraMatchesProfile(OsmCameraNode camera, NodeProfile profile) {
|
||||
/// Check if a node matches a specific profile (all profile tags must match)
|
||||
bool _nodeMatchesProfile(OsmCameraNode node, NodeProfile profile) {
|
||||
for (final entry in profile.tags.entries) {
|
||||
if (camera.tags[entry.key] != entry.value) return false;
|
||||
if (node.tags[entry.key] != entry.value) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ class MapViewState extends State<MapView> {
|
||||
mapBounds = null;
|
||||
}
|
||||
final cameras = (mapBounds != null)
|
||||
? cameraProvider.getCachedCamerasForBounds(mapBounds)
|
||||
? cameraProvider.getCachedNodesForBounds(mapBounds)
|
||||
: <OsmCameraNode>[];
|
||||
|
||||
final markers = CameraMarkersBuilder.buildCameraMarkers(
|
||||
|
||||
Reference in New Issue
Block a user