diff --git a/lib/app_state.dart b/lib/app_state.dart index c00e255..5649c03 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:latlong2/latlong.dart'; -import 'models/camera_profile.dart'; +import 'models/node_profile.dart'; import 'models/operator_profile.dart'; import 'models/osm_camera_node.dart'; import 'models/pending_upload.dart'; @@ -61,9 +61,9 @@ class AppState extends ChangeNotifier { String get username => _authState.username; // Profile state - List get profiles => _profileState.profiles; - List get enabledProfiles => _profileState.enabledProfiles; - bool isEnabled(CameraProfile p) => _profileState.isEnabled(p); + List get profiles => _profileState.profiles; + List get enabledProfiles => _profileState.enabledProfiles; + bool isEnabled(NodeProfile p) => _profileState.isEnabled(p); // Operator profile state List get operatorProfiles => _operatorProfileState.profiles; @@ -134,15 +134,15 @@ class AppState extends ChangeNotifier { } // ---------- Profile Methods ---------- - void toggleProfile(CameraProfile p, bool e) { + void toggleProfile(NodeProfile p, bool e) { _profileState.toggleProfile(p, e); } - void addOrUpdateProfile(CameraProfile p) { + void addOrUpdateProfile(NodeProfile p) { _profileState.addOrUpdateProfile(p); } - void deleteProfile(CameraProfile p) { + void deleteProfile(NodeProfile p) { _profileState.deleteProfile(p); } @@ -166,7 +166,7 @@ class AppState extends ChangeNotifier { void updateSession({ double? directionDeg, - CameraProfile? profile, + NodeProfile? profile, OperatorProfile? operatorProfile, LatLng? target, }) { @@ -180,7 +180,7 @@ class AppState extends ChangeNotifier { void updateEditSession({ double? directionDeg, - CameraProfile? profile, + NodeProfile? profile, OperatorProfile? operatorProfile, LatLng? target, }) { diff --git a/lib/models/camera_profile.dart b/lib/models/node_profile.dart similarity index 89% rename from lib/models/camera_profile.dart rename to lib/models/node_profile.dart index 5b48904..92caa7f 100644 --- a/lib/models/camera_profile.dart +++ b/lib/models/node_profile.dart @@ -1,7 +1,7 @@ import 'package:uuid/uuid.dart'; -/// A bundle of preset OSM tags that describe a particular camera model/type. -class CameraProfile { +/// A bundle of preset OSM tags that describe a particular surveillance node model/type. +class NodeProfile { final String id; final String name; final Map tags; @@ -10,7 +10,7 @@ class CameraProfile { final bool submittable; final bool editable; - CameraProfile({ + NodeProfile({ required this.id, required this.name, required this.tags, @@ -21,7 +21,7 @@ class CameraProfile { }); /// Built‑in default: Generic ALPR camera (customizable template, not submittable) - factory CameraProfile.genericAlpr() => CameraProfile( + factory NodeProfile.genericAlpr() => NodeProfile( id: 'builtin-generic-alpr', name: 'Generic ALPR', tags: const { @@ -35,7 +35,7 @@ class CameraProfile { ); /// Built‑in: Flock Safety ALPR camera - factory CameraProfile.flock() => CameraProfile( + factory NodeProfile.flock() => NodeProfile( id: 'builtin-flock', name: 'Flock', tags: const { @@ -54,7 +54,7 @@ class CameraProfile { ); /// Built‑in: Motorola Solutions/Vigilant ALPR camera - factory CameraProfile.motorola() => CameraProfile( + factory NodeProfile.motorola() => NodeProfile( id: 'builtin-motorola', name: 'Motorola/Vigilant', tags: const { @@ -73,7 +73,7 @@ class CameraProfile { ); /// Built‑in: Genetec ALPR camera - factory CameraProfile.genetec() => CameraProfile( + factory NodeProfile.genetec() => NodeProfile( id: 'builtin-genetec', name: 'Genetec', tags: const { @@ -92,7 +92,7 @@ class CameraProfile { ); /// Built‑in: Leonardo/ELSAG ALPR camera - factory CameraProfile.leonardo() => CameraProfile( + factory NodeProfile.leonardo() => NodeProfile( id: 'builtin-leonardo', name: 'Leonardo/ELSAG', tags: const { @@ -111,7 +111,7 @@ class CameraProfile { ); /// Built‑in: Neology ALPR camera - factory CameraProfile.neology() => CameraProfile( + factory NodeProfile.neology() => NodeProfile( id: 'builtin-neology', name: 'Neology', tags: const { @@ -129,7 +129,7 @@ class CameraProfile { ); /// Built‑in: Generic gunshot detector (customizable template, not submittable) - factory CameraProfile.genericGunshotDetector() => CameraProfile( + factory NodeProfile.genericGunshotDetector() => NodeProfile( id: 'builtin-generic-gunshot', name: 'Generic Gunshot Detector', tags: const { @@ -143,7 +143,7 @@ class CameraProfile { ); /// Built‑in: ShotSpotter gunshot detector - factory CameraProfile.shotspotter() => CameraProfile( + factory NodeProfile.shotspotter() => NodeProfile( id: 'builtin-shotspotter', name: 'ShotSpotter', tags: const { @@ -160,7 +160,7 @@ class CameraProfile { ); /// Built‑in: Flock Raven gunshot detector - factory CameraProfile.flockRaven() => CameraProfile( + factory NodeProfile.flockRaven() => NodeProfile( id: 'builtin-flock-raven', name: 'Flock Raven', tags: const { @@ -179,7 +179,7 @@ class CameraProfile { /// Returns true if this profile can be used for submissions bool get isSubmittable => submittable; - CameraProfile copyWith({ + NodeProfile copyWith({ String? id, String? name, Map? tags, @@ -188,7 +188,7 @@ class CameraProfile { bool? submittable, bool? editable, }) => - CameraProfile( + NodeProfile( id: id ?? this.id, name: name ?? this.name, tags: tags ?? this.tags, @@ -208,7 +208,7 @@ class CameraProfile { 'editable': editable, }; - factory CameraProfile.fromJson(Map j) => CameraProfile( + factory NodeProfile.fromJson(Map j) => NodeProfile( id: j['id'], name: j['name'], tags: Map.from(j['tags']), @@ -221,7 +221,7 @@ class CameraProfile { @override bool operator ==(Object other) => identical(this, other) || - other is CameraProfile && + other is NodeProfile && runtimeType == other.runtimeType && id == other.id; diff --git a/lib/models/pending_upload.dart b/lib/models/pending_upload.dart index 37fc8c7..45159d6 100644 --- a/lib/models/pending_upload.dart +++ b/lib/models/pending_upload.dart @@ -1,12 +1,12 @@ import 'package:latlong2/latlong.dart'; -import 'camera_profile.dart'; +import 'node_profile.dart'; import 'operator_profile.dart'; import '../state/settings_state.dart'; class PendingUpload { final LatLng coord; final double direction; - final CameraProfile profile; + final NodeProfile profile; 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 @@ -75,8 +75,8 @@ class PendingUpload { coord: LatLng(j['lat'], j['lon']), direction: j['dir'], profile: j['profile'] is Map - ? CameraProfile.fromJson(j['profile']) - : CameraProfile.genericAlpr(), + ? NodeProfile.fromJson(j['profile']) + : NodeProfile.genericAlpr(), operatorProfile: j['operatorProfile'] != null ? OperatorProfile.fromJson(j['operatorProfile']) : null, diff --git a/lib/screens/profile_editor.dart b/lib/screens/profile_editor.dart index f751900..74f0609 100644 --- a/lib/screens/profile_editor.dart +++ b/lib/screens/profile_editor.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:uuid/uuid.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; import '../app_state.dart'; class ProfileEditor extends StatefulWidget { const ProfileEditor({super.key, required this.profile}); - final CameraProfile profile; + final NodeProfile profile; @override State createState() => _ProfileEditorState(); diff --git a/lib/screens/settings_screen_sections/profile_list_section.dart b/lib/screens/settings_screen_sections/profile_list_section.dart index 06e749e..bd88894 100644 --- a/lib/screens/settings_screen_sections/profile_list_section.dart +++ b/lib/screens/settings_screen_sections/profile_list_section.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import 'package:provider/provider.dart'; import '../../app_state.dart'; -import '../../models/camera_profile.dart'; +import '../../models/node_profile.dart'; import '../profile_editor.dart'; class ProfileListSection extends StatelessWidget { @@ -17,13 +17,13 @@ class ProfileListSection extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('Camera Profiles', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const Text('Node Profiles', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), TextButton.icon( onPressed: () => Navigator.push( context, MaterialPageRoute( builder: (_) => ProfileEditor( - profile: CameraProfile( + profile: NodeProfile( id: const Uuid().v4(), name: '', tags: const {}, @@ -111,7 +111,7 @@ class ProfileListSection extends StatelessWidget { ); } -void _showDeleteProfileDialog(BuildContext context, CameraProfile profile) { +void _showDeleteProfileDialog(BuildContext context, NodeProfile profile) { final appState = context.read(); showDialog( context: context, diff --git a/lib/services/map_data_provider.dart b/lib/services/map_data_provider.dart index 1ed5810..c4be440 100644 --- a/lib/services/map_data_provider.dart +++ b/lib/services/map_data_provider.dart @@ -2,12 +2,12 @@ import 'package:latlong2/latlong.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter/foundation.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; import '../models/osm_camera_node.dart'; import '../app_state.dart'; -import 'map_data_submodules/cameras_from_overpass.dart'; +import 'map_data_submodules/nodes_from_overpass.dart'; import 'map_data_submodules/tiles_from_remote.dart'; -import 'map_data_submodules/cameras_from_local.dart'; +import 'map_data_submodules/nodes_from_local.dart'; import 'map_data_submodules/tiles_from_local.dart'; enum MapSource { local, remote, auto } // For future use @@ -33,9 +33,9 @@ class MapDataProvider { /// Fetch surveillance nodes from OSM/Overpass or local storage. /// Remote is default. If source is MapSource.auto, remote is tried first unless offline. - Future> getCameras({ + Future> getNodes({ required LatLngBounds bounds, - required List profiles, + required List profiles, UploadMode uploadMode = UploadMode.production, MapSource source = MapSource.auto, }) async { @@ -46,7 +46,7 @@ class MapDataProvider { if (offline) { throw OfflineModeException("Cannot fetch remote nodes in offline mode."); } - return camerasFromOverpass( + return fetchOverpassNodes( bounds: bounds, profiles: profiles, uploadMode: uploadMode, @@ -56,7 +56,7 @@ class MapDataProvider { // Explicit local request: always use local if (source == MapSource.local) { - return fetchLocalCameras( + return fetchLocalNodes( bounds: bounds, profiles: profiles, ); @@ -64,14 +64,15 @@ class MapDataProvider { // AUTO: default = remote first, fallback to local only if offline if (offline) { - return fetchLocalCameras( + return fetchLocalNodes( bounds: bounds, profiles: profiles, + maxNodes: AppState.instance.maxCameras, ); } else { // Try remote, fallback to local ONLY if remote throws (optional, could be removed for stricter behavior) try { - return await camerasFromOverpass( + return await fetchOverpassNodes( bounds: bounds, profiles: profiles, uploadMode: uploadMode, @@ -79,20 +80,20 @@ class MapDataProvider { ); } catch (e) { debugPrint('[MapDataProvider] Remote node fetch failed, error: $e. Falling back to local.'); - return fetchLocalCameras( - bounds: bounds, - profiles: profiles, - maxCameras: AppState.instance.maxCameras, - ); + return fetchLocalNodes( + bounds: bounds, + profiles: profiles, + maxNodes: AppState.instance.maxCameras, + ); } } } /// Bulk/paged node fetch for offline downloads (handling paging, dedup, and Overpass retries) /// Only use for offline area download, not for map browsing! Ignores maxCameras config. - Future> getAllCamerasForDownload({ + Future> getAllNodesForDownload({ required LatLngBounds bounds, - required List profiles, + required List profiles, UploadMode uploadMode = UploadMode.production, int pageSize = 500, int maxTries = 3, @@ -101,7 +102,7 @@ class MapDataProvider { if (offline) { throw OfflineModeException("Cannot fetch remote nodes for offline area download in offline mode."); } - return camerasFromOverpass( + return fetchOverpassNodes( bounds: bounds, profiles: profiles, uploadMode: uploadMode, diff --git a/lib/services/map_data_submodules/cameras_from_local.dart b/lib/services/map_data_submodules/nodes_from_local.dart similarity index 58% rename from lib/services/map_data_submodules/cameras_from_local.dart rename to lib/services/map_data_submodules/nodes_from_local.dart index 86f4d54..2d049e5 100644 --- a/lib/services/map_data_submodules/cameras_from_local.dart +++ b/lib/services/map_data_submodules/nodes_from_local.dart @@ -3,15 +3,15 @@ import 'dart:convert'; import 'package:latlong2/latlong.dart'; import 'package:flutter_map/flutter_map.dart' show LatLngBounds; import '../../models/osm_camera_node.dart'; -import '../../models/camera_profile.dart'; +import '../../models/node_profile.dart'; import '../offline_area_service.dart'; import '../offline_areas/offline_area_models.dart'; -/// Fetch camera nodes from all offline areas intersecting the bounds/profile list. -Future> fetchLocalCameras({ +/// Fetch surveillance nodes from all offline areas intersecting the bounds/profile list. +Future> fetchLocalNodes({ required LatLngBounds bounds, - required List profiles, - int? maxCameras, + required List profiles, + int? maxNodes, }) async { final areas = OfflineAreaService().offlineAreas; final Map deduped = {}; @@ -20,24 +20,24 @@ Future> fetchLocalCameras({ if (area.status != OfflineAreaStatus.complete) continue; if (!area.bounds.isOverlapping(bounds)) continue; - final nodes = await _loadAreaCameras(area); - for (final cam in nodes) { - // Deduplicate by camera ID, preferring the first occurrence - if (deduped.containsKey(cam.id)) continue; + final nodes = await _loadAreaNodes(area); + for (final node in nodes) { + // Deduplicate by node ID, preferring the first occurrence + if (deduped.containsKey(node.id)) continue; // Within view bounds? - if (!_pointInBounds(cam.coord, bounds)) continue; + if (!_pointInBounds(node.coord, bounds)) continue; // Profile filter if used - if (profiles.isNotEmpty && !_matchesAnyProfile(cam, profiles)) continue; - deduped[cam.id] = cam; + if (profiles.isNotEmpty && !_matchesAnyProfile(node, profiles)) continue; + deduped[node.id] = node; } } - final out = deduped.values.take(maxCameras ?? deduped.length).toList(); + final out = deduped.values.take(maxNodes ?? deduped.length).toList(); return out; } // Try in-memory first, else load from disk -Future> _loadAreaCameras(OfflineArea area) async { +Future> _loadAreaNodes(OfflineArea area) async { if (area.cameras.isNotEmpty) { return area.cameras; } @@ -57,16 +57,16 @@ bool _pointInBounds(LatLng pt, LatLngBounds bounds) { pt.longitude <= bounds.northEast.longitude; } -bool _matchesAnyProfile(OsmCameraNode cam, List profiles) { +bool _matchesAnyProfile(OsmCameraNode node, List profiles) { for (final prof in profiles) { - if (_cameraMatchesProfile(cam, prof)) return true; + if (_nodeMatchesProfile(node, prof)) return true; } return false; } -bool _cameraMatchesProfile(OsmCameraNode cam, CameraProfile profile) { +bool _nodeMatchesProfile(OsmCameraNode node, NodeProfile profile) { for (final e in profile.tags.entries) { - if (cam.tags[e.key] != e.value) return false; // All profile tags must match + if (node.tags[e.key] != e.value) return false; // All profile tags must match } return true; } diff --git a/lib/services/map_data_submodules/cameras_from_overpass.dart b/lib/services/map_data_submodules/nodes_from_overpass.dart similarity index 80% rename from lib/services/map_data_submodules/cameras_from_overpass.dart rename to lib/services/map_data_submodules/nodes_from_overpass.dart index c9a6dc9..e15c9cd 100644 --- a/lib/services/map_data_submodules/cameras_from_overpass.dart +++ b/lib/services/map_data_submodules/nodes_from_overpass.dart @@ -4,15 +4,15 @@ import 'package:flutter/foundation.dart'; import 'package:latlong2/latlong.dart'; import 'package:flutter_map/flutter_map.dart'; -import '../../models/camera_profile.dart'; +import '../../models/node_profile.dart'; import '../../models/osm_camera_node.dart'; import '../../app_state.dart'; import '../network_status.dart'; /// Fetches surveillance nodes from the Overpass OSM API for the given bounds and profiles. -Future> camerasFromOverpass({ +Future> fetchOverpassNodes({ required LatLngBounds bounds, - required List profiles, + required List profiles, UploadMode uploadMode = UploadMode.production, required int maxResults, }) async { @@ -24,8 +24,8 @@ Future> camerasFromOverpass({ final query = _buildOverpassQuery(bounds, profiles, maxResults); try { - debugPrint('[camerasFromOverpass] Querying Overpass for surveillance nodes...'); - debugPrint('[camerasFromOverpass] Query:\n$query'); + debugPrint('[fetchOverpassNodes] Querying Overpass for surveillance nodes...'); + debugPrint('[fetchOverpassNodes] Query:\n$query'); final response = await http.post( Uri.parse(overpassEndpoint), @@ -33,7 +33,7 @@ Future> camerasFromOverpass({ ); if (response.statusCode != 200) { - debugPrint('[camerasFromOverpass] Overpass API error: ${response.body}'); + debugPrint('[fetchOverpassNodes] Overpass API error: ${response.body}'); NetworkStatus.instance.reportOverpassIssue(); return []; } @@ -42,7 +42,7 @@ Future> camerasFromOverpass({ final elements = data['elements'] as List; if (elements.length > 20) { - debugPrint('[camerasFromOverpass] Retrieved ${elements.length} surveillance nodes'); + debugPrint('[fetchOverpassNodes] Retrieved ${elements.length} surveillance nodes'); } NetworkStatus.instance.reportOverpassSuccess(); @@ -56,7 +56,7 @@ Future> camerasFromOverpass({ }).toList(); } catch (e) { - debugPrint('[camerasFromOverpass] Exception: $e'); + debugPrint('[fetchOverpassNodes] Exception: $e'); // Report network issues for connection errors if (e.toString().contains('Connection refused') || @@ -70,7 +70,7 @@ Future> camerasFromOverpass({ } /// Builds an Overpass API query for surveillance nodes matching the given profiles within bounds. -String _buildOverpassQuery(LatLngBounds bounds, List profiles, int maxResults) { +String _buildOverpassQuery(LatLngBounds bounds, List profiles, int maxResults) { // Build node clauses for each profile final nodeClauses = profiles.map((profile) { // Convert profile tags to Overpass filter format diff --git a/lib/services/offline_areas/offline_area_downloader.dart b/lib/services/offline_areas/offline_area_downloader.dart index 42d7f30..9ac6076 100644 --- a/lib/services/offline_areas/offline_area_downloader.dart +++ b/lib/services/offline_areas/offline_area_downloader.dart @@ -147,7 +147,7 @@ class OfflineAreaDownloader { }) async { // Calculate expanded camera bounds that cover the entire tile area at minimum zoom final cameraBounds = _calculateCameraBounds(bounds, minZoom); - final cameras = await MapDataProvider().getAllCamerasForDownload( + final cameras = await MapDataProvider().getAllNodesForDownload( bounds: cameraBounds, profiles: AppState.instance.profiles, // Use ALL profiles, not just enabled ones ); diff --git a/lib/services/profile_service.dart b/lib/services/profile_service.dart index 3ee9ddc..aa5d836 100644 --- a/lib/services/profile_service.dart +++ b/lib/services/profile_service.dart @@ -1,20 +1,20 @@ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; class ProfileService { static const _key = 'custom_profiles'; - Future> load() async { + Future> load() async { final prefs = await SharedPreferences.getInstance(); final jsonStr = prefs.getString(_key); if (jsonStr == null) return []; final list = jsonDecode(jsonStr) as List; - return list.map((e) => CameraProfile.fromJson(e)).toList(); + return list.map((e) => NodeProfile.fromJson(e)).toList(); } - Future save(List profiles) async { + Future save(List profiles) async { final prefs = await SharedPreferences.getInstance(); // MUST convert to List before jsonEncode; the previous MappedIterable diff --git a/lib/state/profile_state.dart b/lib/state/profile_state.dart index 3c96167..4363e58 100644 --- a/lib/state/profile_state.dart +++ b/lib/state/profile_state.dart @@ -1,33 +1,33 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; import '../services/profile_service.dart'; class ProfileState extends ChangeNotifier { static const String _enabledPrefsKey = 'enabled_profiles'; - final List _profiles = []; - final Set _enabled = {}; + final List _profiles = []; + final Set _enabled = {}; // Getters - List get profiles => List.unmodifiable(_profiles); - bool isEnabled(CameraProfile p) => _enabled.contains(p); - List get enabledProfiles => + List get profiles => List.unmodifiable(_profiles); + bool isEnabled(NodeProfile p) => _enabled.contains(p); + List get enabledProfiles => _profiles.where(isEnabled).toList(growable: false); // Initialize profiles from built-in and custom sources Future init() async { // Initialize profiles: built-in + custom - _profiles.add(CameraProfile.genericAlpr()); - _profiles.add(CameraProfile.flock()); - _profiles.add(CameraProfile.motorola()); - _profiles.add(CameraProfile.genetec()); - _profiles.add(CameraProfile.leonardo()); - _profiles.add(CameraProfile.neology()); - _profiles.add(CameraProfile.genericGunshotDetector()); - _profiles.add(CameraProfile.shotspotter()); - _profiles.add(CameraProfile.flockRaven()); + _profiles.add(NodeProfile.genericAlpr()); + _profiles.add(NodeProfile.flock()); + _profiles.add(NodeProfile.motorola()); + _profiles.add(NodeProfile.genetec()); + _profiles.add(NodeProfile.leonardo()); + _profiles.add(NodeProfile.neology()); + _profiles.add(NodeProfile.genericGunshotDetector()); + _profiles.add(NodeProfile.shotspotter()); + _profiles.add(NodeProfile.flockRaven()); _profiles.addAll(await ProfileService().load()); // Load enabled profile IDs from prefs @@ -42,7 +42,7 @@ class ProfileState extends ChangeNotifier { } } - void toggleProfile(CameraProfile p, bool e) { + void toggleProfile(NodeProfile p, bool e) { if (e) { _enabled.add(p); } else { @@ -57,7 +57,7 @@ class ProfileState extends ChangeNotifier { notifyListeners(); } - void addOrUpdateProfile(CameraProfile p) { + void addOrUpdateProfile(NodeProfile p) { final idx = _profiles.indexWhere((x) => x.id == p.id); if (idx >= 0) { _profiles[idx] = p; @@ -70,7 +70,7 @@ class ProfileState extends ChangeNotifier { notifyListeners(); } - void deleteProfile(CameraProfile p) { + void deleteProfile(NodeProfile p) { if (!p.editable) return; _enabled.remove(p); _profiles.removeWhere((x) => x.id == p.id); diff --git a/lib/state/session_state.dart b/lib/state/session_state.dart index 3d0abda..ab60d0c 100644 --- a/lib/state/session_state.dart +++ b/lib/state/session_state.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; import '../models/operator_profile.dart'; import '../models/osm_camera_node.dart'; // ------------------ AddNodeSession ------------------ class AddNodeSession { AddNodeSession({required this.profile, this.directionDegrees = 0}); - CameraProfile profile; + NodeProfile profile; OperatorProfile? operatorProfile; double directionDegrees; LatLng? target; @@ -24,7 +24,7 @@ class EditNodeSession { }); final OsmCameraNode originalNode; // The original node being edited - CameraProfile profile; + NodeProfile profile; OperatorProfile? operatorProfile; double directionDegrees; LatLng target; // Current position (can be dragged) @@ -38,7 +38,7 @@ class SessionState extends ChangeNotifier { AddNodeSession? get session => _session; EditNodeSession? get editSession => _editSession; - void startAddSession(List enabledProfiles) { + void startAddSession(List enabledProfiles) { final submittableProfiles = enabledProfiles.where((p) => p.isSubmittable).toList(); final defaultProfile = submittableProfiles.isNotEmpty ? submittableProfiles.first @@ -48,11 +48,11 @@ class SessionState extends ChangeNotifier { notifyListeners(); } - void startEditSession(OsmCameraNode node, List enabledProfiles) { + void startEditSession(OsmCameraNode node, List enabledProfiles) { final submittableProfiles = enabledProfiles.where((p) => p.isSubmittable).toList(); // Try to find a matching profile based on the node's tags - CameraProfile matchingProfile = submittableProfiles.isNotEmpty + NodeProfile matchingProfile = submittableProfiles.isNotEmpty ? submittableProfiles.first : enabledProfiles.first; @@ -74,7 +74,7 @@ class SessionState extends ChangeNotifier { notifyListeners(); } - bool _profileMatchesTags(CameraProfile profile, Map tags) { + bool _profileMatchesTags(NodeProfile profile, Map tags) { // Simple matching: check if all profile tags are present in node tags for (final entry in profile.tags.entries) { if (tags[entry.key] != entry.value) { @@ -86,7 +86,7 @@ class SessionState extends ChangeNotifier { void updateSession({ double? directionDeg, - CameraProfile? profile, + NodeProfile? profile, OperatorProfile? operatorProfile, LatLng? target, }) { @@ -114,7 +114,7 @@ class SessionState extends ChangeNotifier { void updateEditSession({ double? directionDeg, - CameraProfile? profile, + NodeProfile? profile, OperatorProfile? operatorProfile, LatLng? target, }) { diff --git a/lib/widgets/add_node_sheet.dart b/lib/widgets/add_node_sheet.dart index 8234a44..88e3abf 100644 --- a/lib/widgets/add_node_sheet.dart +++ b/lib/widgets/add_node_sheet.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../app_state.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; import '../models/operator_profile.dart'; import 'refine_tags_sheet.dart'; @@ -64,7 +64,7 @@ class AddNodeSheet extends StatelessWidget { const SizedBox(height: 16), ListTile( title: const Text('Profile'), - trailing: DropdownButton( + trailing: DropdownButton( value: session.profile, items: submittableProfiles .map((p) => DropdownMenuItem(value: p, child: Text(p.name))) diff --git a/lib/widgets/camera_provider_with_cache.dart b/lib/widgets/camera_provider_with_cache.dart index 3e0c9f0..b733556 100644 --- a/lib/widgets/camera_provider_with_cache.dart +++ b/lib/widgets/camera_provider_with_cache.dart @@ -6,7 +6,7 @@ import 'package:flutter_map/flutter_map.dart' show LatLngBounds; import '../services/map_data_provider.dart'; import '../services/camera_cache.dart'; import '../services/network_status.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; import '../models/osm_camera_node.dart'; import '../app_state.dart'; @@ -38,7 +38,7 @@ class CameraProviderWithCache extends ChangeNotifier { /// and notifies listeners/UI when new data is available. void fetchAndUpdate({ required LatLngBounds bounds, - required List profiles, + required List profiles, UploadMode uploadMode = UploadMode.production, }) { // Fast: serve cached immediately @@ -48,7 +48,7 @@ class CameraProviderWithCache extends ChangeNotifier { _debounceTimer = Timer(const Duration(milliseconds: 400), () async { try { // Use MapSource.auto to handle both offline and online modes appropriately - final fresh = await MapDataProvider().getCameras( + final fresh = await MapDataProvider().getNodes( bounds: bounds, profiles: profiles, uploadMode: uploadMode, @@ -79,7 +79,7 @@ class CameraProviderWithCache extends ChangeNotifier { } /// Check if a camera matches any of the provided profiles - bool _matchesAnyProfile(OsmCameraNode camera, List profiles) { + bool _matchesAnyProfile(OsmCameraNode camera, List profiles) { for (final profile in profiles) { if (_cameraMatchesProfile(camera, profile)) return true; } @@ -87,7 +87,7 @@ class CameraProviderWithCache extends ChangeNotifier { } /// Check if a camera matches a specific profile (all profile tags must match) - bool _cameraMatchesProfile(OsmCameraNode camera, CameraProfile profile) { + bool _cameraMatchesProfile(OsmCameraNode camera, NodeProfile profile) { for (final entry in profile.tags.entries) { if (camera.tags[entry.key] != entry.value) return false; } diff --git a/lib/widgets/edit_node_sheet.dart b/lib/widgets/edit_node_sheet.dart index 2ef90bd..0b07b88 100644 --- a/lib/widgets/edit_node_sheet.dart +++ b/lib/widgets/edit_node_sheet.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../app_state.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; import '../models/operator_profile.dart'; import '../state/settings_state.dart'; import 'refine_tags_sheet.dart'; @@ -71,7 +71,7 @@ class EditNodeSheet extends StatelessWidget { const SizedBox(height: 16), ListTile( title: const Text('Profile'), - trailing: DropdownButton( + trailing: DropdownButton( value: session.profile, items: submittableProfiles .map((p) => DropdownMenuItem(value: p, child: Text(p.name))) diff --git a/lib/widgets/map/camera_refresh_controller.dart b/lib/widgets/map/camera_refresh_controller.dart index ae94771..bb0f38b 100644 --- a/lib/widgets/map/camera_refresh_controller.dart +++ b/lib/widgets/map/camera_refresh_controller.dart @@ -3,7 +3,7 @@ import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; -import '../../models/camera_profile.dart'; +import '../../models/node_profile.dart'; import '../../app_state.dart' show UploadMode; import '../camera_provider_with_cache.dart'; import '../../dev_config.dart'; @@ -12,7 +12,7 @@ import '../../dev_config.dart'; /// Handles debounced camera fetching and profile-based cache invalidation. class CameraRefreshController { late final CameraProviderWithCache _cameraProvider; - List? _lastEnabledProfiles; + List? _lastEnabledProfiles; VoidCallback? _onCamerasUpdated; /// Initialize the camera refresh controller @@ -32,7 +32,7 @@ class CameraRefreshController { /// Check if camera profiles changed and handle cache clearing if needed. /// Returns true if profiles changed (triggering a refresh). bool checkAndHandleProfileChanges({ - required List currentEnabledProfiles, + required List currentEnabledProfiles, required VoidCallback onProfilesChanged, }) { if (_lastEnabledProfiles == null || @@ -57,7 +57,7 @@ class CameraRefreshController { /// Refresh cameras from provider for the current map view void refreshCamerasFromProvider({ required AnimatedMapController controller, - required List enabledProfiles, + required List enabledProfiles, required UploadMode uploadMode, required BuildContext context, }) { @@ -93,7 +93,7 @@ class CameraRefreshController { CameraProviderWithCache get cameraProvider => _cameraProvider; /// Helper to check if two profile lists are equal by comparing IDs - bool _profileListsEqual(List list1, List list2) { + bool _profileListsEqual(List list1, List list2) { if (list1.length != list2.length) return false; // Compare by profile IDs since profiles are value objects final ids1 = list1.map((p) => p.id).toSet(); diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index b4ade94..9299220 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -8,7 +8,7 @@ import '../app_state.dart'; import '../services/offline_area_service.dart'; import '../services/network_status.dart'; import '../models/osm_camera_node.dart'; -import '../models/camera_profile.dart'; +import '../models/node_profile.dart'; import '../models/tile_provider.dart'; import 'debouncer.dart'; import 'camera_provider_with_cache.dart';