mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
nodes, not cameras
This commit is contained in:
@@ -17,7 +17,7 @@ import 'state/upload_queue_state.dart';
|
||||
|
||||
// Re-export types
|
||||
export 'state/settings_state.dart' show UploadMode, FollowMeMode;
|
||||
export 'state/session_state.dart' show AddCameraSession, EditCameraSession;
|
||||
export 'state/session_state.dart' show AddNodeSession, EditNodeSession;
|
||||
|
||||
// ------------------ AppState ------------------
|
||||
class AppState extends ChangeNotifier {
|
||||
@@ -69,8 +69,8 @@ class AppState extends ChangeNotifier {
|
||||
List<OperatorProfile> get operatorProfiles => _operatorProfileState.profiles;
|
||||
|
||||
// Session state
|
||||
AddCameraSession? get session => _sessionState.session;
|
||||
EditCameraSession? get editSession => _sessionState.editSession;
|
||||
AddNodeSession? get session => _sessionState.session;
|
||||
EditNodeSession? get editSession => _sessionState.editSession;
|
||||
|
||||
// Settings state
|
||||
bool get offlineMode => _settingsState.offlineMode;
|
||||
|
||||
@@ -28,8 +28,8 @@ const double kAddPinYOffset = 0.0;
|
||||
const String kClientName = 'FlockMap';
|
||||
const String kClientVersion = '0.9.7';
|
||||
|
||||
// Marker/camera interaction
|
||||
const int kCameraMinZoomLevel = 10; // Minimum zoom to show cameras or warning
|
||||
// Marker/node interaction
|
||||
const int kCameraMinZoomLevel = 10; // Minimum zoom to show nodes or warning
|
||||
const Duration kMarkerTapTimeout = Duration(milliseconds: 250);
|
||||
const Duration kDebounceCameraRefresh = Duration(milliseconds: 500);
|
||||
|
||||
@@ -62,8 +62,8 @@ const int kAbsoluteMaxZoom = 19;
|
||||
const double kCameraIconDiameter = 20.0;
|
||||
const double kCameraRingThickness = 4.0;
|
||||
const double kCameraDotOpacity = 0.4; // Opacity for the grey dot interior
|
||||
const Color kCameraRingColorReal = Color(0xC43F55F3); // Real cameras from OSM - blue
|
||||
const Color kCameraRingColorMock = Color(0xC4FFFFFF); // Add camera mock point - white
|
||||
const Color kCameraRingColorPending = Color(0xC49C27B0); // Submitted/pending cameras - purple
|
||||
const Color kCameraRingColorEditing = Color(0xC4FF9800); // Camera being edited - orange
|
||||
const Color kCameraRingColorPendingEdit = Color(0xC4757575); // Original camera with pending edit - grey
|
||||
const Color kCameraRingColorReal = Color(0xC43F55F3); // Real nodes from OSM - blue
|
||||
const Color kCameraRingColorMock = Color(0xC4FFFFFF); // Add node mock point - white
|
||||
const Color kCameraRingColorPending = Color(0xC49C27B0); // Submitted/pending nodes - purple
|
||||
const Color kCameraRingColorEditing = Color(0xC4FF9800); // Node being edited - orange
|
||||
const Color kCameraRingColorPendingEdit = Color(0xC4757575); // Original node with pending edit - grey
|
||||
@@ -7,8 +7,8 @@ import '../app_state.dart';
|
||||
import '../dev_config.dart';
|
||||
import '../widgets/map_view.dart';
|
||||
|
||||
import '../widgets/add_camera_sheet.dart';
|
||||
import '../widgets/edit_camera_sheet.dart';
|
||||
import '../widgets/add_node_sheet.dart';
|
||||
import '../widgets/edit_node_sheet.dart';
|
||||
import '../widgets/camera_provider_with_cache.dart';
|
||||
import '../widgets/download_area_dialog.dart';
|
||||
|
||||
@@ -70,7 +70,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void _openAddCameraSheet() {
|
||||
void _openAddNodeSheet() {
|
||||
final appState = context.read<AppState>();
|
||||
// Disable follow-me when adding a camera so the map doesn't jump around
|
||||
appState.setFollowMeMode(FollowMeMode.off);
|
||||
@@ -79,11 +79,11 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
final session = appState.session!; // guaranteed non‑null now
|
||||
|
||||
_scaffoldKey.currentState!.showBottomSheet(
|
||||
(ctx) => AddCameraSheet(session: session),
|
||||
(ctx) => AddNodeSheet(session: session),
|
||||
);
|
||||
}
|
||||
|
||||
void _openEditCameraSheet() {
|
||||
void _openEditNodeSheet() {
|
||||
final appState = context.read<AppState>();
|
||||
// Disable follow-me when editing a camera so the map doesn't jump around
|
||||
appState.setFollowMeMode(FollowMeMode.off);
|
||||
@@ -91,7 +91,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
final session = appState.editSession!; // should be non-null when this is called
|
||||
|
||||
_scaffoldKey.currentState!.showBottomSheet(
|
||||
(ctx) => EditCameraSheet(session: session),
|
||||
(ctx) => EditNodeSheet(session: session),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
// Auto-open edit sheet when edit session starts
|
||||
if (appState.editSession != null && !_editSheetShown) {
|
||||
_editSheetShown = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _openEditCameraSheet());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _openEditNodeSheet());
|
||||
} else if (appState.editSession == null) {
|
||||
_editSheetShown = false;
|
||||
}
|
||||
@@ -169,8 +169,8 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
icon: Icon(Icons.add_location_alt),
|
||||
label: Text('Tag Camera'),
|
||||
onPressed: _openAddCameraSheet,
|
||||
label: Text('Tag Node'),
|
||||
onPressed: _openAddNodeSheet,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: Size(0, 48),
|
||||
textStyle: TextStyle(fontSize: 16),
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'settings_screen_sections/queue_section.dart';
|
||||
import 'settings_screen_sections/offline_areas_section.dart';
|
||||
import 'settings_screen_sections/offline_mode_section.dart';
|
||||
import 'settings_screen_sections/about_section.dart';
|
||||
import 'settings_screen_sections/max_cameras_section.dart';
|
||||
import 'settings_screen_sections/max_nodes_section.dart';
|
||||
import 'settings_screen_sections/tile_provider_section.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
@@ -30,7 +30,7 @@ class SettingsScreen extends StatelessWidget {
|
||||
Divider(),
|
||||
OperatorProfileListSection(),
|
||||
Divider(),
|
||||
MaxCamerasSection(),
|
||||
MaxNodesSection(),
|
||||
Divider(),
|
||||
TileProviderSection(),
|
||||
Divider(),
|
||||
|
||||
@@ -2,21 +2,21 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../app_state.dart';
|
||||
|
||||
class MaxCamerasSection extends StatefulWidget {
|
||||
const MaxCamerasSection({super.key});
|
||||
class MaxNodesSection extends StatefulWidget {
|
||||
const MaxNodesSection({super.key});
|
||||
|
||||
@override
|
||||
State<MaxCamerasSection> createState() => _MaxCamerasSectionState();
|
||||
State<MaxNodesSection> createState() => _MaxNodesSectionState();
|
||||
}
|
||||
|
||||
class _MaxCamerasSectionState extends State<MaxCamerasSection> {
|
||||
class _MaxNodesSectionState extends State<MaxNodesSection> {
|
||||
late TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final maxCameras = context.read<AppState>().maxCameras;
|
||||
_controller = TextEditingController(text: maxCameras.toString());
|
||||
final maxNodes = context.read<AppState>().maxCameras;
|
||||
_controller = TextEditingController(text: maxNodes.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -35,11 +35,11 @@ class _MaxCamerasSectionState extends State<MaxCamerasSection> {
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.filter_alt),
|
||||
title: const Text('Max cameras fetched/drawn'),
|
||||
title: const Text('Max nodes fetched/drawn'),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Set an upper limit for the number of cameras on the map (default: 250).'),
|
||||
const Text('Set an upper limit for the number of nodes on the map (default: 250).'),
|
||||
if (showWarning)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
@@ -40,7 +40,7 @@ class OperatorProfileListSection extends StatelessWidget {
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'No operator profiles defined. Create one to apply operator tags to camera submissions.',
|
||||
'No operator profiles defined. Create one to apply operator tags to node submissions.',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@@ -31,7 +31,7 @@ class MapDataProvider {
|
||||
AppState.instance.setOfflineMode(enabled);
|
||||
}
|
||||
|
||||
/// Fetch cameras from OSM/Overpass or local storage.
|
||||
/// Fetch surveillance nodes from OSM/Overpass or local storage.
|
||||
/// Remote is default. If source is MapSource.auto, remote is tried first unless offline.
|
||||
Future<List<OsmCameraNode>> getCameras({
|
||||
required LatLngBounds bounds,
|
||||
@@ -44,7 +44,7 @@ class MapDataProvider {
|
||||
// Explicit remote request: error if offline, else always remote
|
||||
if (source == MapSource.remote) {
|
||||
if (offline) {
|
||||
throw OfflineModeException("Cannot fetch remote cameras in offline mode.");
|
||||
throw OfflineModeException("Cannot fetch remote nodes in offline mode.");
|
||||
}
|
||||
return camerasFromOverpass(
|
||||
bounds: bounds,
|
||||
@@ -79,7 +79,7 @@ class MapDataProvider {
|
||||
pageSize: AppState.instance.maxCameras,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[MapDataProvider] Remote camera fetch failed, error: $e. Falling back to local.');
|
||||
debugPrint('[MapDataProvider] Remote node fetch failed, error: $e. Falling back to local.');
|
||||
return fetchLocalCameras(
|
||||
bounds: bounds,
|
||||
profiles: profiles,
|
||||
@@ -89,7 +89,7 @@ class MapDataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/// Bulk/paged camera fetch for offline downloads (handling paging, dedup, and Overpass retries)
|
||||
/// 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<List<OsmCameraNode>> getAllCamerasForDownload({
|
||||
required LatLngBounds bounds,
|
||||
@@ -100,7 +100,7 @@ class MapDataProvider {
|
||||
}) async {
|
||||
final offline = AppState.instance.offlineMode;
|
||||
if (offline) {
|
||||
throw OfflineModeException("Cannot fetch remote cameras for offline area download in offline mode.");
|
||||
throw OfflineModeException("Cannot fetch remote nodes for offline area download in offline mode.");
|
||||
}
|
||||
return camerasFromOverpass(
|
||||
bounds: bounds,
|
||||
|
||||
@@ -14,7 +14,7 @@ class Uploader {
|
||||
|
||||
Future<bool> upload(PendingUpload p) async {
|
||||
try {
|
||||
print('Uploader: Starting upload for camera at ${p.coord.latitude}, ${p.coord.longitude}');
|
||||
print('Uploader: Starting upload for node at ${p.coord.latitude}, ${p.coord.longitude}');
|
||||
|
||||
// 1. open changeset
|
||||
final action = p.isEdit ? 'Update' : 'Add';
|
||||
@@ -22,7 +22,7 @@ class Uploader {
|
||||
<osm>
|
||||
<changeset>
|
||||
<tag k="created_by" v="$kClientName $kClientVersion"/>
|
||||
<tag k="comment" v="$action ${p.profile.name} surveillance camera"/>
|
||||
<tag k="comment" v="$action ${p.profile.name} surveillance node"/>
|
||||
</changeset>
|
||||
</osm>''';
|
||||
print('Uploader: Creating changeset...');
|
||||
|
||||
@@ -5,25 +5,25 @@ import '../models/camera_profile.dart';
|
||||
import '../models/operator_profile.dart';
|
||||
import '../models/osm_camera_node.dart';
|
||||
|
||||
// ------------------ AddCameraSession ------------------
|
||||
class AddCameraSession {
|
||||
AddCameraSession({required this.profile, this.directionDegrees = 0});
|
||||
// ------------------ AddNodeSession ------------------
|
||||
class AddNodeSession {
|
||||
AddNodeSession({required this.profile, this.directionDegrees = 0});
|
||||
CameraProfile profile;
|
||||
OperatorProfile? operatorProfile;
|
||||
double directionDegrees;
|
||||
LatLng? target;
|
||||
}
|
||||
|
||||
// ------------------ EditCameraSession ------------------
|
||||
class EditCameraSession {
|
||||
EditCameraSession({
|
||||
// ------------------ EditNodeSession ------------------
|
||||
class EditNodeSession {
|
||||
EditNodeSession({
|
||||
required this.originalNode,
|
||||
required this.profile,
|
||||
required this.directionDegrees,
|
||||
required this.target,
|
||||
});
|
||||
|
||||
final OsmCameraNode originalNode; // The original camera being edited
|
||||
final OsmCameraNode originalNode; // The original node being edited
|
||||
CameraProfile profile;
|
||||
OperatorProfile? operatorProfile;
|
||||
double directionDegrees;
|
||||
@@ -31,19 +31,19 @@ class EditCameraSession {
|
||||
}
|
||||
|
||||
class SessionState extends ChangeNotifier {
|
||||
AddCameraSession? _session;
|
||||
EditCameraSession? _editSession;
|
||||
AddNodeSession? _session;
|
||||
EditNodeSession? _editSession;
|
||||
|
||||
// Getters
|
||||
AddCameraSession? get session => _session;
|
||||
EditCameraSession? get editSession => _editSession;
|
||||
AddNodeSession? get session => _session;
|
||||
EditNodeSession? get editSession => _editSession;
|
||||
|
||||
void startAddSession(List<CameraProfile> enabledProfiles) {
|
||||
final submittableProfiles = enabledProfiles.where((p) => p.isSubmittable).toList();
|
||||
final defaultProfile = submittableProfiles.isNotEmpty
|
||||
? submittableProfiles.first
|
||||
: enabledProfiles.first; // Fallback to any enabled profile
|
||||
_session = AddCameraSession(profile: defaultProfile);
|
||||
_session = AddNodeSession(profile: defaultProfile);
|
||||
_editSession = null; // Clear any edit session
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class SessionState extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
_editSession = EditCameraSession(
|
||||
_editSession = EditNodeSession(
|
||||
originalNode: node,
|
||||
profile: matchingProfile,
|
||||
directionDegrees: node.directionDeg ?? 0,
|
||||
@@ -150,7 +150,7 @@ class SessionState extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
AddCameraSession? commitSession() {
|
||||
AddNodeSession? commitSession() {
|
||||
if (_session?.target == null) return null;
|
||||
|
||||
final session = _session!;
|
||||
@@ -159,7 +159,7 @@ class SessionState extends ChangeNotifier {
|
||||
return session;
|
||||
}
|
||||
|
||||
EditCameraSession? commitEditSession() {
|
||||
EditNodeSession? commitEditSession() {
|
||||
if (_editSession == null) return null;
|
||||
|
||||
final session = _editSession!;
|
||||
|
||||
@@ -25,7 +25,7 @@ class UploadQueueState extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Add a completed session to the upload queue
|
||||
void addFromSession(AddCameraSession session, {required UploadMode uploadMode}) {
|
||||
void addFromSession(AddNodeSession session, {required UploadMode uploadMode}) {
|
||||
final upload = PendingUpload(
|
||||
coord: session.target!,
|
||||
direction: session.directionDegrees,
|
||||
@@ -58,7 +58,7 @@ class UploadQueueState extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Add a completed edit session to the upload queue
|
||||
void addFromEditSession(EditCameraSession session, {required UploadMode uploadMode}) {
|
||||
void addFromEditSession(EditNodeSession session, {required UploadMode uploadMode}) {
|
||||
final upload = PendingUpload(
|
||||
coord: session.target,
|
||||
direction: session.directionDegrees,
|
||||
|
||||
@@ -6,10 +6,10 @@ import '../models/camera_profile.dart';
|
||||
import '../models/operator_profile.dart';
|
||||
import 'refine_tags_sheet.dart';
|
||||
|
||||
class AddCameraSheet extends StatelessWidget {
|
||||
const AddCameraSheet({super.key, required this.session});
|
||||
class AddNodeSheet extends StatelessWidget {
|
||||
const AddNodeSheet({super.key, required this.session});
|
||||
|
||||
final AddCameraSession session;
|
||||
final AddNodeSession session;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -19,7 +19,7 @@ class AddCameraSheet extends StatelessWidget {
|
||||
appState.commitSession();
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Camera queued for upload')),
|
||||
const SnackBar(content: Text('Node queued for upload')),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ class AddCameraSheet extends StatelessWidget {
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Enable a submittable profile in Settings to submit new cameras.',
|
||||
'Enable a submittable profile in Settings to submit new nodes.',
|
||||
style: TextStyle(color: Colors.red, fontSize: 13),
|
||||
),
|
||||
),
|
||||
@@ -127,7 +127,7 @@ class AddCameraSheet extends StatelessWidget {
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'This profile is for map viewing only. Please select a submittable profile to submit new cameras.',
|
||||
'This profile is for map viewing only. Please select a submittable profile to submit new nodes.',
|
||||
style: TextStyle(color: Colors.orange, fontSize: 13),
|
||||
),
|
||||
),
|
||||
@@ -7,10 +7,10 @@ import '../models/operator_profile.dart';
|
||||
import '../state/settings_state.dart';
|
||||
import 'refine_tags_sheet.dart';
|
||||
|
||||
class EditCameraSheet extends StatelessWidget {
|
||||
const EditCameraSheet({super.key, required this.session});
|
||||
class EditNodeSheet extends StatelessWidget {
|
||||
const EditNodeSheet({super.key, required this.session});
|
||||
|
||||
final EditCameraSession session;
|
||||
final EditNodeSession session;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -20,7 +20,7 @@ class EditCameraSheet extends StatelessWidget {
|
||||
appState.commitEditSession();
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Camera edit queued for upload')),
|
||||
const SnackBar(content: Text('Node edit queued for upload')),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class EditCameraSheet extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Edit Camera #${session.originalNode.id}',
|
||||
'Edit Node #${session.originalNode.id}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -118,7 +118,7 @@ class EditCameraSheet extends StatelessWidget {
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Cannot submit edits on production nodes to sandbox. Switch to Production mode in Settings to edit cameras.',
|
||||
'Cannot submit edits on production nodes to sandbox. Switch to Production mode in Settings to edit nodes.',
|
||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
||||
),
|
||||
),
|
||||
@@ -134,7 +134,7 @@ class EditCameraSheet extends StatelessWidget {
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Enable a submittable profile in Settings to edit cameras.',
|
||||
'Enable a submittable profile in Settings to edit nodes.',
|
||||
style: TextStyle(color: Colors.red, fontSize: 13),
|
||||
),
|
||||
),
|
||||
@@ -150,7 +150,7 @@ class EditCameraSheet extends StatelessWidget {
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'This profile is for map viewing only. Please select a submittable profile to edit cameras.',
|
||||
'This profile is for map viewing only. Please select a submittable profile to edit nodes.',
|
||||
style: TextStyle(color: Colors.orange, fontSize: 13),
|
||||
),
|
||||
),
|
||||
@@ -12,8 +12,8 @@ class DirectionConesBuilder {
|
||||
static List<Polygon> buildDirectionCones({
|
||||
required List<OsmCameraNode> cameras,
|
||||
required double zoom,
|
||||
AddCameraSession? session,
|
||||
EditCameraSession? editSession,
|
||||
AddNodeSession? session,
|
||||
EditNodeSession? editSession,
|
||||
}) {
|
||||
final overlays = <Polygon>[];
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import 'layer_selector_button.dart';
|
||||
class MapOverlays extends StatelessWidget {
|
||||
final MapController mapController;
|
||||
final UploadMode uploadMode;
|
||||
final AddCameraSession? session;
|
||||
final EditCameraSession? editSession;
|
||||
final AddNodeSession? session;
|
||||
final EditNodeSession? editSession;
|
||||
final String? attribution; // Attribution for current tile provider
|
||||
|
||||
const MapOverlays({
|
||||
|
||||
@@ -66,7 +66,7 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'Create operator profiles in Settings to apply additional tags to your camera submissions.',
|
||||
'Create operator profiles in Settings to apply additional tags to your node submissions.',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user