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:
klas
2026-02-12 14:57:38 +01:00
commit 40143734fc
125 changed files with 65073 additions and 0 deletions

View 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();
}
}