Flutter web app replacing legacy WPF CCTV surveillance keyboard controller. Includes wall overview, section view with monitor grid, camera input, PTZ control, alarm/lock/sequence BLoCs, and legacy-matching UI styling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
124 lines
3.4 KiB
Dart
124 lines
3.4 KiB
Dart
/// Camera lock entity matching the coordinator's CameraLock model.
|
|
class CameraLock {
|
|
final int cameraId;
|
|
final CameraLockPriority priority;
|
|
final String ownerName;
|
|
final DateTime ownedSince;
|
|
final DateTime expiresAt;
|
|
|
|
const CameraLock({
|
|
required this.cameraId,
|
|
required this.priority,
|
|
required this.ownerName,
|
|
required this.ownedSince,
|
|
required this.expiresAt,
|
|
});
|
|
|
|
factory CameraLock.fromJson(Map<String, dynamic> json) {
|
|
return CameraLock(
|
|
cameraId: json['cameraId'] as int? ?? json['CameraId'] as int? ?? 0,
|
|
priority: CameraLockPriority.fromString(
|
|
json['priority'] as String? ?? json['Priority'] as String? ?? 'Low'),
|
|
ownerName:
|
|
json['ownerName'] as String? ?? json['OwnerName'] as String? ?? '',
|
|
ownedSince: DateTime.parse(
|
|
json['ownedSince'] as String? ?? json['OwnedSince'] as String? ?? ''),
|
|
expiresAt: DateTime.parse(
|
|
json['expiresAt'] as String? ?? json['ExpiresAt'] as String? ?? ''),
|
|
);
|
|
}
|
|
|
|
Duration get timeRemaining {
|
|
final remaining = expiresAt.difference(DateTime.now().toUtc());
|
|
return remaining.isNegative ? Duration.zero : remaining;
|
|
}
|
|
|
|
bool get isExpiringSoon => timeRemaining.inSeconds <= 60;
|
|
bool get isExpired => timeRemaining == Duration.zero;
|
|
bool isOwnedBy(String keyboardId) =>
|
|
ownerName.toLowerCase() == keyboardId.toLowerCase();
|
|
}
|
|
|
|
enum CameraLockPriority {
|
|
none,
|
|
high,
|
|
low;
|
|
|
|
static CameraLockPriority fromString(String value) {
|
|
switch (value.toLowerCase()) {
|
|
case 'high':
|
|
return CameraLockPriority.high;
|
|
case 'low':
|
|
return CameraLockPriority.low;
|
|
default:
|
|
return CameraLockPriority.none;
|
|
}
|
|
}
|
|
|
|
String get name {
|
|
switch (this) {
|
|
case CameraLockPriority.high:
|
|
return 'High';
|
|
case CameraLockPriority.low:
|
|
return 'Low';
|
|
case CameraLockPriority.none:
|
|
return 'None';
|
|
}
|
|
}
|
|
}
|
|
|
|
enum CameraLockNotificationType {
|
|
acquired,
|
|
takenOver,
|
|
confirmTakeOver,
|
|
confirmed,
|
|
rejected,
|
|
expireSoon,
|
|
unlocked;
|
|
|
|
static CameraLockNotificationType fromString(String value) {
|
|
switch (value) {
|
|
case 'Acquired':
|
|
return CameraLockNotificationType.acquired;
|
|
case 'TakenOver':
|
|
return CameraLockNotificationType.takenOver;
|
|
case 'ConfirmTakeOver':
|
|
return CameraLockNotificationType.confirmTakeOver;
|
|
case 'Confirmed':
|
|
return CameraLockNotificationType.confirmed;
|
|
case 'Rejected':
|
|
return CameraLockNotificationType.rejected;
|
|
case 'ExpireSoon':
|
|
return CameraLockNotificationType.expireSoon;
|
|
case 'Unlocked':
|
|
return CameraLockNotificationType.unlocked;
|
|
default:
|
|
return CameraLockNotificationType.acquired;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Lock notification from the coordinator (sent via WebSocket)
|
|
class CameraLockNotification {
|
|
final CameraLockNotificationType type;
|
|
final int cameraId;
|
|
final String copilotName;
|
|
|
|
const CameraLockNotification({
|
|
required this.type,
|
|
required this.cameraId,
|
|
required this.copilotName,
|
|
});
|
|
|
|
factory CameraLockNotification.fromJson(Map<String, dynamic> json) {
|
|
return CameraLockNotification(
|
|
type: CameraLockNotificationType.fromString(
|
|
json['type'] as String? ?? json['Type'] as String? ?? ''),
|
|
cameraId: json['cameraId'] as int? ?? json['CameraId'] as int? ?? 0,
|
|
copilotName: json['copilotName'] as String? ??
|
|
json['CopilotName'] as String? ??
|
|
'',
|
|
);
|
|
}
|
|
}
|