/// 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 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 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? ?? '', ); } }