mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-15 23:50:49 +02:00
all profiles as lists, better handling, stop using a fallback profile for broken submissions without one
This commit is contained in:
+141
-153
@@ -20,161 +20,149 @@ class NodeProfile {
|
|||||||
this.editable = true,
|
this.editable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Built‑in default: Generic ALPR camera (customizable template, not submittable)
|
/// Get all built-in default node profiles
|
||||||
factory NodeProfile.genericAlpr() => NodeProfile(
|
static List<NodeProfile> getDefaults() => [
|
||||||
id: 'builtin-generic-alpr',
|
NodeProfile(
|
||||||
name: 'Generic ALPR',
|
id: 'builtin-generic-alpr',
|
||||||
tags: const {
|
name: 'Generic ALPR',
|
||||||
'man_made': 'surveillance',
|
tags: const {
|
||||||
'surveillance:type': 'ALPR',
|
'man_made': 'surveillance',
|
||||||
},
|
'surveillance:type': 'ALPR',
|
||||||
builtin: true,
|
},
|
||||||
requiresDirection: true,
|
builtin: true,
|
||||||
submittable: false,
|
requiresDirection: true,
|
||||||
editable: false,
|
submittable: false,
|
||||||
);
|
editable: false,
|
||||||
|
),
|
||||||
|
NodeProfile(
|
||||||
|
id: 'builtin-flock',
|
||||||
|
name: 'Flock',
|
||||||
|
tags: const {
|
||||||
|
'man_made': 'surveillance',
|
||||||
|
'surveillance': 'public',
|
||||||
|
'surveillance:type': 'ALPR',
|
||||||
|
'surveillance:zone': 'traffic',
|
||||||
|
'camera:type': 'fixed',
|
||||||
|
'manufacturer': 'Flock Safety',
|
||||||
|
'manufacturer:wikidata': 'Q108485435',
|
||||||
|
},
|
||||||
|
builtin: true,
|
||||||
|
requiresDirection: true,
|
||||||
|
submittable: true,
|
||||||
|
editable: true,
|
||||||
|
),
|
||||||
|
NodeProfile(
|
||||||
|
id: 'builtin-motorola',
|
||||||
|
name: 'Motorola/Vigilant',
|
||||||
|
tags: const {
|
||||||
|
'man_made': 'surveillance',
|
||||||
|
'surveillance': 'public',
|
||||||
|
'surveillance:type': 'ALPR',
|
||||||
|
'surveillance:zone': 'traffic',
|
||||||
|
'camera:type': 'fixed',
|
||||||
|
'manufacturer': 'Motorola Solutions',
|
||||||
|
'manufacturer:wikidata': 'Q634815',
|
||||||
|
},
|
||||||
|
builtin: true,
|
||||||
|
requiresDirection: true,
|
||||||
|
submittable: true,
|
||||||
|
editable: true,
|
||||||
|
),
|
||||||
|
NodeProfile(
|
||||||
|
id: 'builtin-genetec',
|
||||||
|
name: 'Genetec',
|
||||||
|
tags: const {
|
||||||
|
'man_made': 'surveillance',
|
||||||
|
'surveillance': 'public',
|
||||||
|
'surveillance:type': 'ALPR',
|
||||||
|
'surveillance:zone': 'traffic',
|
||||||
|
'camera:type': 'fixed',
|
||||||
|
'manufacturer': 'Genetec',
|
||||||
|
'manufacturer:wikidata': 'Q30295174',
|
||||||
|
},
|
||||||
|
builtin: true,
|
||||||
|
requiresDirection: true,
|
||||||
|
submittable: true,
|
||||||
|
editable: true,
|
||||||
|
),
|
||||||
|
NodeProfile(
|
||||||
|
id: 'builtin-leonardo',
|
||||||
|
name: 'Leonardo/ELSAG',
|
||||||
|
tags: const {
|
||||||
|
'man_made': 'surveillance',
|
||||||
|
'surveillance': 'public',
|
||||||
|
'surveillance:type': 'ALPR',
|
||||||
|
'surveillance:zone': 'traffic',
|
||||||
|
'camera:type': 'fixed',
|
||||||
|
'manufacturer': 'Leonardo',
|
||||||
|
'manufacturer:wikidata': 'Q910379',
|
||||||
|
},
|
||||||
|
builtin: true,
|
||||||
|
requiresDirection: true,
|
||||||
|
submittable: true,
|
||||||
|
editable: true,
|
||||||
|
),
|
||||||
|
NodeProfile(
|
||||||
|
id: 'builtin-neology',
|
||||||
|
name: 'Neology',
|
||||||
|
tags: const {
|
||||||
|
'man_made': 'surveillance',
|
||||||
|
'surveillance': 'public',
|
||||||
|
'surveillance:type': 'ALPR',
|
||||||
|
'surveillance:zone': 'traffic',
|
||||||
|
'camera:type': 'fixed',
|
||||||
|
'manufacturer': 'Neology, Inc.',
|
||||||
|
},
|
||||||
|
builtin: true,
|
||||||
|
requiresDirection: true,
|
||||||
|
submittable: true,
|
||||||
|
editable: true,
|
||||||
|
),
|
||||||
|
NodeProfile(
|
||||||
|
id: 'builtin-generic-gunshot',
|
||||||
|
name: 'Generic Gunshot Detector',
|
||||||
|
tags: const {
|
||||||
|
'man_made': 'surveillance',
|
||||||
|
'surveillance:type': 'gunshot_detector',
|
||||||
|
},
|
||||||
|
builtin: true,
|
||||||
|
requiresDirection: false,
|
||||||
|
submittable: false,
|
||||||
|
editable: false,
|
||||||
|
),
|
||||||
|
NodeProfile(
|
||||||
|
id: 'builtin-shotspotter',
|
||||||
|
name: 'ShotSpotter',
|
||||||
|
tags: const {
|
||||||
|
'man_made': 'surveillance',
|
||||||
|
'surveillance': 'public',
|
||||||
|
'surveillance:type': 'gunshot_detector',
|
||||||
|
'surveillance:brand': 'ShotSpotter',
|
||||||
|
'surveillance:brand:wikidata': 'Q107740188',
|
||||||
|
},
|
||||||
|
builtin: true,
|
||||||
|
requiresDirection: false,
|
||||||
|
submittable: true,
|
||||||
|
editable: true,
|
||||||
|
),
|
||||||
|
NodeProfile(
|
||||||
|
id: 'builtin-flock-raven',
|
||||||
|
name: 'Flock Raven',
|
||||||
|
tags: const {
|
||||||
|
'man_made': 'surveillance',
|
||||||
|
'surveillance': 'public',
|
||||||
|
'surveillance:type': 'gunshot_detector',
|
||||||
|
'brand': 'Flock Safety',
|
||||||
|
'brand:wikidata': 'Q108485435',
|
||||||
|
},
|
||||||
|
builtin: true,
|
||||||
|
requiresDirection: false,
|
||||||
|
submittable: true,
|
||||||
|
editable: true,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
/// Built‑in: Flock Safety ALPR camera
|
|
||||||
factory NodeProfile.flock() => NodeProfile(
|
|
||||||
id: 'builtin-flock',
|
|
||||||
name: 'Flock',
|
|
||||||
tags: const {
|
|
||||||
'man_made': 'surveillance',
|
|
||||||
'surveillance': 'public',
|
|
||||||
'surveillance:type': 'ALPR',
|
|
||||||
'surveillance:zone': 'traffic',
|
|
||||||
'camera:type': 'fixed',
|
|
||||||
'manufacturer': 'Flock Safety',
|
|
||||||
'manufacturer:wikidata': 'Q108485435',
|
|
||||||
},
|
|
||||||
builtin: true,
|
|
||||||
requiresDirection: true,
|
|
||||||
submittable: true,
|
|
||||||
editable: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Built‑in: Motorola Solutions/Vigilant ALPR camera
|
|
||||||
factory NodeProfile.motorola() => NodeProfile(
|
|
||||||
id: 'builtin-motorola',
|
|
||||||
name: 'Motorola/Vigilant',
|
|
||||||
tags: const {
|
|
||||||
'man_made': 'surveillance',
|
|
||||||
'surveillance': 'public',
|
|
||||||
'surveillance:type': 'ALPR',
|
|
||||||
'surveillance:zone': 'traffic',
|
|
||||||
'camera:type': 'fixed',
|
|
||||||
'manufacturer': 'Motorola Solutions',
|
|
||||||
'manufacturer:wikidata': 'Q634815',
|
|
||||||
},
|
|
||||||
builtin: true,
|
|
||||||
requiresDirection: true,
|
|
||||||
submittable: true,
|
|
||||||
editable: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Built‑in: Genetec ALPR camera
|
|
||||||
factory NodeProfile.genetec() => NodeProfile(
|
|
||||||
id: 'builtin-genetec',
|
|
||||||
name: 'Genetec',
|
|
||||||
tags: const {
|
|
||||||
'man_made': 'surveillance',
|
|
||||||
'surveillance': 'public',
|
|
||||||
'surveillance:type': 'ALPR',
|
|
||||||
'surveillance:zone': 'traffic',
|
|
||||||
'camera:type': 'fixed',
|
|
||||||
'manufacturer': 'Genetec',
|
|
||||||
'manufacturer:wikidata': 'Q30295174',
|
|
||||||
},
|
|
||||||
builtin: true,
|
|
||||||
requiresDirection: true,
|
|
||||||
submittable: true,
|
|
||||||
editable: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Built‑in: Leonardo/ELSAG ALPR camera
|
|
||||||
factory NodeProfile.leonardo() => NodeProfile(
|
|
||||||
id: 'builtin-leonardo',
|
|
||||||
name: 'Leonardo/ELSAG',
|
|
||||||
tags: const {
|
|
||||||
'man_made': 'surveillance',
|
|
||||||
'surveillance': 'public',
|
|
||||||
'surveillance:type': 'ALPR',
|
|
||||||
'surveillance:zone': 'traffic',
|
|
||||||
'camera:type': 'fixed',
|
|
||||||
'manufacturer': 'Leonardo',
|
|
||||||
'manufacturer:wikidata': 'Q910379',
|
|
||||||
},
|
|
||||||
builtin: true,
|
|
||||||
requiresDirection: true,
|
|
||||||
submittable: true,
|
|
||||||
editable: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Built‑in: Neology ALPR camera
|
|
||||||
factory NodeProfile.neology() => NodeProfile(
|
|
||||||
id: 'builtin-neology',
|
|
||||||
name: 'Neology',
|
|
||||||
tags: const {
|
|
||||||
'man_made': 'surveillance',
|
|
||||||
'surveillance': 'public',
|
|
||||||
'surveillance:type': 'ALPR',
|
|
||||||
'surveillance:zone': 'traffic',
|
|
||||||
'camera:type': 'fixed',
|
|
||||||
'manufacturer': 'Neology, Inc.',
|
|
||||||
},
|
|
||||||
builtin: true,
|
|
||||||
requiresDirection: true,
|
|
||||||
submittable: true,
|
|
||||||
editable: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Built‑in: Generic gunshot detector (customizable template, not submittable)
|
|
||||||
factory NodeProfile.genericGunshotDetector() => NodeProfile(
|
|
||||||
id: 'builtin-generic-gunshot',
|
|
||||||
name: 'Generic Gunshot Detector',
|
|
||||||
tags: const {
|
|
||||||
'man_made': 'surveillance',
|
|
||||||
'surveillance:type': 'gunshot_detector',
|
|
||||||
},
|
|
||||||
builtin: true,
|
|
||||||
requiresDirection: false,
|
|
||||||
submittable: false,
|
|
||||||
editable: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Built‑in: ShotSpotter gunshot detector
|
|
||||||
factory NodeProfile.shotspotter() => NodeProfile(
|
|
||||||
id: 'builtin-shotspotter',
|
|
||||||
name: 'ShotSpotter',
|
|
||||||
tags: const {
|
|
||||||
'man_made': 'surveillance',
|
|
||||||
'surveillance': 'public',
|
|
||||||
'surveillance:type': 'gunshot_detector',
|
|
||||||
'surveillance:brand': 'ShotSpotter',
|
|
||||||
'surveillance:brand:wikidata': 'Q107740188',
|
|
||||||
},
|
|
||||||
builtin: true,
|
|
||||||
requiresDirection: false,
|
|
||||||
submittable: true,
|
|
||||||
editable: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Built‑in: Flock Raven gunshot detector
|
|
||||||
factory NodeProfile.flockRaven() => NodeProfile(
|
|
||||||
id: 'builtin-flock-raven',
|
|
||||||
name: 'Flock Raven',
|
|
||||||
tags: const {
|
|
||||||
'man_made': 'surveillance',
|
|
||||||
'surveillance': 'public',
|
|
||||||
'surveillance:type': 'gunshot_detector',
|
|
||||||
'brand': 'Flock Safety',
|
|
||||||
'brand:wikidata': 'Q108485435',
|
|
||||||
},
|
|
||||||
builtin: true,
|
|
||||||
requiresDirection: false,
|
|
||||||
submittable: true,
|
|
||||||
editable: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Returns true if this profile can be used for submissions
|
/// Returns true if this profile can be used for submissions
|
||||||
bool get isSubmittable => submittable;
|
bool get isSubmittable => submittable;
|
||||||
|
|||||||
@@ -44,15 +44,6 @@ class OperatorProfile {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Built-in default: Lowe's operator profile
|
|
||||||
factory OperatorProfile.lowes() => getDefaults()[0];
|
|
||||||
|
|
||||||
/// Built-in default: The Home Depot operator profile
|
|
||||||
factory OperatorProfile.homeDepot() => getDefaults()[1];
|
|
||||||
|
|
||||||
/// Built-in default: Simon Property Group operator profile
|
|
||||||
factory OperatorProfile.simonPropertyGroup() => getDefaults()[2];
|
|
||||||
|
|
||||||
OperatorProfile copyWith({
|
OperatorProfile copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
String? name,
|
String? name,
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class PendingUpload {
|
|||||||
direction: j['dir'],
|
direction: j['dir'],
|
||||||
profile: j['profile'] is Map<String, dynamic>
|
profile: j['profile'] is Map<String, dynamic>
|
||||||
? NodeProfile.fromJson(j['profile'])
|
? NodeProfile.fromJson(j['profile'])
|
||||||
: NodeProfile.genericAlpr(),
|
: throw Exception('PendingUpload missing required profile data - this should never happen'),
|
||||||
operatorProfile: j['operatorProfile'] != null
|
operatorProfile: j['operatorProfile'] != null
|
||||||
? OperatorProfile.fromJson(j['operatorProfile'])
|
? OperatorProfile.fromJson(j['operatorProfile'])
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -23,19 +23,7 @@ class ProfileState extends ChangeNotifier {
|
|||||||
|
|
||||||
// Add built-in profiles if this is first launch
|
// Add built-in profiles if this is first launch
|
||||||
if (addDefaults) {
|
if (addDefaults) {
|
||||||
final builtinProfiles = [
|
_profiles.addAll(NodeProfile.getDefaults());
|
||||||
NodeProfile.genericAlpr(),
|
|
||||||
NodeProfile.flock(),
|
|
||||||
NodeProfile.motorola(),
|
|
||||||
NodeProfile.genetec(),
|
|
||||||
NodeProfile.leonardo(),
|
|
||||||
NodeProfile.neology(),
|
|
||||||
NodeProfile.genericGunshotDetector(),
|
|
||||||
NodeProfile.shotspotter(),
|
|
||||||
NodeProfile.flockRaven(),
|
|
||||||
];
|
|
||||||
|
|
||||||
_profiles.addAll(builtinProfiles);
|
|
||||||
await ProfileService().save(_profiles);
|
await ProfileService().save(_profiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:deflockapp/models/pending_upload.dart';
|
|
||||||
import 'package:deflockapp/models/node_profile.dart';
|
|
||||||
import 'package:deflockapp/state/settings_state.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('PendingUpload', () {
|
|
||||||
test('should serialize and deserialize upload mode correctly', () {
|
|
||||||
// Test each upload mode
|
|
||||||
final testModes = [
|
|
||||||
UploadMode.production,
|
|
||||||
UploadMode.sandbox,
|
|
||||||
UploadMode.simulate,
|
|
||||||
];
|
|
||||||
|
|
||||||
for (final mode in testModes) {
|
|
||||||
final original = PendingUpload(
|
|
||||||
coord: LatLng(37.7749, -122.4194),
|
|
||||||
direction: 90.0,
|
|
||||||
profile: NodeProfile.flock(),
|
|
||||||
uploadMode: mode,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Serialize to JSON
|
|
||||||
final json = original.toJson();
|
|
||||||
|
|
||||||
// Deserialize from JSON
|
|
||||||
final restored = PendingUpload.fromJson(json);
|
|
||||||
|
|
||||||
// Verify upload mode is preserved
|
|
||||||
expect(restored.uploadMode, equals(mode));
|
|
||||||
expect(restored.uploadModeDisplayName, equals(original.uploadModeDisplayName));
|
|
||||||
|
|
||||||
// Verify other fields too
|
|
||||||
expect(restored.coord.latitude, equals(original.coord.latitude));
|
|
||||||
expect(restored.coord.longitude, equals(original.coord.longitude));
|
|
||||||
expect(restored.direction, equals(original.direction));
|
|
||||||
expect(restored.profile.id, equals(original.profile.id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle legacy JSON without uploadMode', () {
|
|
||||||
// Simulate old JSON format without uploadMode field
|
|
||||||
final legacyJson = {
|
|
||||||
'lat': 37.7749,
|
|
||||||
'lon': -122.4194,
|
|
||||||
'dir': 90.0,
|
|
||||||
'profile': NodeProfile.flock().toJson(),
|
|
||||||
'originalNodeId': null,
|
|
||||||
'attempts': 0,
|
|
||||||
'error': false,
|
|
||||||
// Note: no 'uploadMode' field
|
|
||||||
};
|
|
||||||
|
|
||||||
final upload = PendingUpload.fromJson(legacyJson);
|
|
||||||
|
|
||||||
// Should default to production mode for legacy entries
|
|
||||||
expect(upload.uploadMode, equals(UploadMode.production));
|
|
||||||
expect(upload.uploadModeDisplayName, equals('Production'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should correctly identify edits vs new cameras', () {
|
|
||||||
final newCamera = PendingUpload(
|
|
||||||
coord: LatLng(37.7749, -122.4194),
|
|
||||||
direction: 90.0,
|
|
||||||
profile: NodeProfile.flock(),
|
|
||||||
uploadMode: UploadMode.production,
|
|
||||||
);
|
|
||||||
|
|
||||||
final editCamera = PendingUpload(
|
|
||||||
coord: LatLng(37.7749, -122.4194),
|
|
||||||
direction: 90.0,
|
|
||||||
profile: CameraProfile.flock(),
|
|
||||||
uploadMode: UploadMode.production,
|
|
||||||
originalNodeId: 12345,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(newCamera.isEdit, isFalse);
|
|
||||||
expect(editCamera.isEdit, isTrue);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user