Initial commit: COPILOT D6 Flutter keyboard controller
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>
This commit is contained in:
168
copilot_keyboard/lib/presentation/blocs/lock/lock_bloc.dart
Normal file
168
copilot_keyboard/lib/presentation/blocs/lock/lock_bloc.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../data/services/coordination_service.dart';
|
||||
import '../../../domain/entities/camera_lock.dart';
|
||||
import 'lock_event.dart';
|
||||
import 'lock_state.dart';
|
||||
|
||||
class LockBloc extends Bloc<LockEvent, LockState> {
|
||||
final CoordinationService _coordinationService;
|
||||
|
||||
StreamSubscription? _locksSub;
|
||||
StreamSubscription? _notifSub;
|
||||
StreamSubscription? _connSub;
|
||||
|
||||
LockBloc({
|
||||
required CoordinationService coordinationService,
|
||||
required String keyboardId,
|
||||
}) : _coordinationService = coordinationService,
|
||||
super(const LockState()) {
|
||||
on<TryLock>(_onTryLock);
|
||||
on<ReleaseLock>(_onReleaseLock);
|
||||
on<ReleaseAllLocks>(_onReleaseAllLocks);
|
||||
on<RequestTakeover>(_onRequestTakeover);
|
||||
on<ConfirmTakeover>(_onConfirmTakeover);
|
||||
on<ResetLockExpiration>(_onResetLockExpiration);
|
||||
on<LocksUpdated>(_onLocksUpdated);
|
||||
on<LockNotificationReceived>(_onLockNotificationReceived);
|
||||
on<CoordinatorConnectionChanged>(_onCoordinatorConnectionChanged);
|
||||
|
||||
// Subscribe to coordinator streams
|
||||
_locksSub = _coordinationService.locks.listen((locks) {
|
||||
add(LocksUpdated(locks));
|
||||
});
|
||||
|
||||
_notifSub = _coordinationService.notifications.listen((notification) {
|
||||
if (notification != null) {
|
||||
add(LockNotificationReceived(notification));
|
||||
}
|
||||
});
|
||||
|
||||
_connSub = _coordinationService.connected.listen((connected) {
|
||||
add(CoordinatorConnectionChanged(connected));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onTryLock(TryLock event, Emitter<LockState> emit) async {
|
||||
final result = await _coordinationService.tryLock(
|
||||
event.cameraId,
|
||||
priority: event.priority,
|
||||
);
|
||||
|
||||
if (!result.acquired) {
|
||||
final lock = result.lock;
|
||||
final owner = lock?.ownerName ?? 'unknown';
|
||||
emit(state.copyWith(
|
||||
lastNotification: 'Camera ${event.cameraId} locked by $owner',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onReleaseLock(
|
||||
ReleaseLock event, Emitter<LockState> emit) async {
|
||||
await _coordinationService.releaseLock(event.cameraId);
|
||||
}
|
||||
|
||||
Future<void> _onReleaseAllLocks(
|
||||
ReleaseAllLocks event, Emitter<LockState> emit) async {
|
||||
final myLocks = await _coordinationService.getMyLockedCameras();
|
||||
for (final cameraId in myLocks) {
|
||||
await _coordinationService.releaseLock(cameraId);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onRequestTakeover(
|
||||
RequestTakeover event, Emitter<LockState> emit) async {
|
||||
final success = await _coordinationService.requestTakeover(
|
||||
event.cameraId,
|
||||
priority: event.priority,
|
||||
);
|
||||
|
||||
if (success) {
|
||||
emit(state.copyWith(
|
||||
lastNotification: 'Takeover requested for camera ${event.cameraId}',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onConfirmTakeover(
|
||||
ConfirmTakeover event, Emitter<LockState> emit) async {
|
||||
await _coordinationService.confirmTakeover(
|
||||
event.cameraId, event.confirm);
|
||||
emit(state.copyWith(clearPendingTakeover: true));
|
||||
}
|
||||
|
||||
Future<void> _onResetLockExpiration(
|
||||
ResetLockExpiration event, Emitter<LockState> emit) async {
|
||||
await _coordinationService.resetExpiration(event.cameraId);
|
||||
}
|
||||
|
||||
void _onLocksUpdated(LocksUpdated event, Emitter<LockState> emit) {
|
||||
emit(state.copyWith(locks: event.locks));
|
||||
}
|
||||
|
||||
void _onLockNotificationReceived(
|
||||
LockNotificationReceived event, Emitter<LockState> emit) {
|
||||
final notification = event.notification;
|
||||
|
||||
switch (notification.type) {
|
||||
case CameraLockNotificationType.confirmTakeOver:
|
||||
// Show takeover confirmation dialog
|
||||
emit(state.copyWith(
|
||||
pendingTakeover: TakeoverRequest(
|
||||
cameraId: notification.cameraId,
|
||||
requestingKeyboard: notification.copilotName,
|
||||
),
|
||||
));
|
||||
break;
|
||||
|
||||
case CameraLockNotificationType.takenOver:
|
||||
emit(state.copyWith(
|
||||
lastNotification:
|
||||
'Camera ${notification.cameraId} taken over by ${notification.copilotName}',
|
||||
));
|
||||
break;
|
||||
|
||||
case CameraLockNotificationType.expireSoon:
|
||||
emit(state.copyWith(
|
||||
lastNotification:
|
||||
'Lock on camera ${notification.cameraId} expiring soon',
|
||||
));
|
||||
break;
|
||||
|
||||
case CameraLockNotificationType.confirmed:
|
||||
emit(state.copyWith(
|
||||
lastNotification:
|
||||
'Takeover confirmed for camera ${notification.cameraId}',
|
||||
));
|
||||
break;
|
||||
|
||||
case CameraLockNotificationType.rejected:
|
||||
emit(state.copyWith(
|
||||
lastNotification:
|
||||
'Takeover rejected for camera ${notification.cameraId}',
|
||||
));
|
||||
break;
|
||||
|
||||
case CameraLockNotificationType.unlocked:
|
||||
case CameraLockNotificationType.acquired:
|
||||
// Handled by lock state updates
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _onCoordinatorConnectionChanged(
|
||||
CoordinatorConnectionChanged event, Emitter<LockState> emit) {
|
||||
emit(state.copyWith(coordinatorConnected: event.connected));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_locksSub?.cancel();
|
||||
_notifSub?.cancel();
|
||||
_connSub?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user