diff --git a/lib/models/camera_profile.dart b/lib/models/camera_profile.dart index e27ac69..210b757 100644 --- a/lib/models/camera_profile.dart +++ b/lib/models/camera_profile.dart @@ -14,19 +14,93 @@ class CameraProfile { this.builtin = false, }); - /// Built‑in default: Generic Flock ALPR camera - factory CameraProfile.alpr() => CameraProfile( - id: 'builtin-alpr', - name: 'Generic Flock', + /// Built‑in default: Generic ALPR camera (view-only) + factory CameraProfile.genericAlpr() => CameraProfile( + id: 'builtin-generic-alpr', + name: 'Generic ALPR', tags: const { 'man_made': 'surveillance', 'surveillance:type': 'ALPR', + }, + builtin: true, + ); + + /// Built‑in: Flock Safety ALPR camera + factory CameraProfile.flock() => CameraProfile( + id: 'builtin-flock', + name: 'Flock', + tags: const { + 'man_made': 'surveillance', + 'surveillance:type': 'ALPR', + 'camera:type': 'fixed', 'manufacturer': 'Flock Safety', 'manufacturer:wikidata': 'Q108485435', }, builtin: true, ); + /// Built‑in: Motorola Solutions/Vigilant ALPR camera + factory CameraProfile.motorola() => CameraProfile( + id: 'builtin-motorola', + name: 'Motorola/Vigilant', + tags: const { + 'man_made': 'surveillance', + 'surveillance:type': 'ALPR', + 'camera:type': 'fixed', + 'manufacturer': 'Motorola Solutions', + 'manufacturer:wikidata': 'Q634815', + }, + builtin: true, + ); + + /// Built‑in: Genetec ALPR camera + factory CameraProfile.genetec() => CameraProfile( + id: 'builtin-genetec', + name: 'Genetec', + tags: const { + 'man_made': 'surveillance', + 'surveillance:type': 'ALPR', + 'camera:type': 'fixed', + 'manufacturer': 'Genetec', + 'manufacturer:wikidata': 'Q30295174', + }, + builtin: true, + ); + + /// Built‑in: Leonardo/ELSAG ALPR camera + factory CameraProfile.leonardo() => CameraProfile( + id: 'builtin-leonardo', + name: 'Leonardo/ELSAG', + tags: const { + 'man_made': 'surveillance', + 'surveillance:type': 'ALPR', + 'camera:type': 'fixed', + 'manufacturer': 'Leonardo', + 'manufacturer:wikidata': 'Q910379', + }, + builtin: true, + ); + + /// Built‑in: Neology ALPR camera + factory CameraProfile.neology() => CameraProfile( + id: 'builtin-neology', + name: 'Neology', + tags: const { + 'man_made': 'surveillance', + 'surveillance:type': 'ALPR', + 'camera:type': 'fixed', + 'manufacturer': 'Neology, Inc.', + }, + builtin: true, + ); + + /// Returns true if this profile can be used for submissions + bool get isSubmittable { + if (!builtin) return true; // All custom profiles are submittable + // Only the generic ALPR builtin profile is not submittable + return id != 'builtin-generic-alpr'; + } + CameraProfile copyWith({ String? id, String? name, diff --git a/lib/models/pending_upload.dart b/lib/models/pending_upload.dart index ae47afc..ac51c84 100644 --- a/lib/models/pending_upload.dart +++ b/lib/models/pending_upload.dart @@ -30,7 +30,7 @@ class PendingUpload { direction: j['dir'], profile: j['profile'] is Map ? CameraProfile.fromJson(j['profile']) - : CameraProfile.alpr(), + : CameraProfile.genericAlpr(), attempts: j['attempts'] ?? 0, error: j['error'] ?? false, ); diff --git a/lib/state/profile_state.dart b/lib/state/profile_state.dart index c847446..5031868 100644 --- a/lib/state/profile_state.dart +++ b/lib/state/profile_state.dart @@ -19,7 +19,12 @@ class ProfileState extends ChangeNotifier { // Initialize profiles from built-in and custom sources Future init() async { // Initialize profiles: built-in + custom - _profiles.add(CameraProfile.alpr()); + _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.addAll(await ProfileService().load()); // Load enabled profile IDs from prefs diff --git a/lib/state/session_state.dart b/lib/state/session_state.dart index 3c25c85..cefb920 100644 --- a/lib/state/session_state.dart +++ b/lib/state/session_state.dart @@ -18,7 +18,11 @@ class SessionState extends ChangeNotifier { AddCameraSession? get session => _session; void startAddSession(List enabledProfiles) { - _session = AddCameraSession(profile: enabledProfiles.first); + 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); notifyListeners(); } diff --git a/lib/widgets/add_camera_sheet.dart b/lib/widgets/add_camera_sheet.dart index 2a7f4d9..81c3eb9 100644 --- a/lib/widgets/add_camera_sheet.dart +++ b/lib/widgets/add_camera_sheet.dart @@ -26,8 +26,8 @@ class AddCameraSheet extends StatelessWidget { Navigator.pop(context); } - final customProfiles = appState.enabledProfiles.where((p) => !p.builtin).toList(); - final allowSubmit = customProfiles.isNotEmpty && !session.profile.builtin; + final submittableProfiles = appState.enabledProfiles.where((p) => p.isSubmittable).toList(); + final allowSubmit = submittableProfiles.isNotEmpty && session.profile.isSubmittable; return Padding( padding: @@ -49,7 +49,7 @@ class AddCameraSheet extends StatelessWidget { title: const Text('Profile'), trailing: DropdownButton( value: session.profile, - items: appState.enabledProfiles + items: submittableProfiles .map((p) => DropdownMenuItem(value: p, child: Text(p.name))) .toList(), onChanged: (p) => @@ -67,7 +67,7 @@ class AddCameraSheet extends StatelessWidget { onChanged: (v) => appState.updateSession(directionDeg: v), ), ), - if (customProfiles.isEmpty) + if (submittableProfiles.isEmpty) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( @@ -76,14 +76,14 @@ class AddCameraSheet extends StatelessWidget { SizedBox(width: 6), Expanded( child: Text( - 'Enable or create a custom profile in Settings to submit new cameras.', + 'Enable a submittable profile in Settings to submit new cameras.', style: TextStyle(color: Colors.red, fontSize: 13), ), ), ], ), ) - else if (session.profile.builtin) + else if (!session.profile.isSubmittable) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( @@ -92,7 +92,7 @@ class AddCameraSheet extends StatelessWidget { SizedBox(width: 6), Expanded( child: Text( - 'The built-in profile is for map viewing only. Please select a custom profile to submit new cameras.', + 'This profile is for map viewing only. Please select a submittable profile to submit new cameras.', style: TextStyle(color: Colors.orange, fontSize: 13), ), ),