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,64 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../config/app_config.dart';
import '../../../data/services/bridge_service.dart';
import 'camera_event.dart';
import 'camera_state.dart';
class CameraBloc extends Bloc<CameraEvent, CameraState> {
final BridgeService _bridgeService;
final AppConfig _config;
CameraBloc({
required BridgeService bridgeService,
required AppConfig config,
}) : _bridgeService = bridgeService,
_config = config,
super(CameraState(availableCameras: config.allCameraIds)) {
on<SelectCamera>(_onSelectCamera);
on<ConnectCameraToMonitor>(_onConnectCameraToMonitor);
on<ClearCameraSelection>(_onClearCameraSelection);
}
void _onSelectCamera(
SelectCamera event,
Emitter<CameraState> emit,
) {
emit(state.copyWith(selectedCameraId: event.cameraId, error: null));
}
Future<void> _onConnectCameraToMonitor(
ConnectCameraToMonitor event,
Emitter<CameraState> emit,
) async {
emit(state.copyWith(isConnecting: true, error: null));
try {
final success = await _bridgeService.viewerConnectLive(
event.monitorId,
event.cameraId,
);
if (success) {
emit(state.copyWith(isConnecting: false));
} else {
emit(state.copyWith(
isConnecting: false,
error: 'Failed to connect camera ${event.cameraId} to monitor ${event.monitorId}',
));
}
} catch (e) {
emit(state.copyWith(
isConnecting: false,
error: e.toString(),
));
}
}
void _onClearCameraSelection(
ClearCameraSelection event,
Emitter<CameraState> emit,
) {
emit(state.copyWith(clearSelection: true, error: null));
}
}

View File

@@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';
abstract class CameraEvent extends Equatable {
const CameraEvent();
@override
List<Object?> get props => [];
}
/// Select a camera for viewing/control
class SelectCamera extends CameraEvent {
final int cameraId;
const SelectCamera(this.cameraId);
@override
List<Object?> get props => [cameraId];
}
/// Connect selected camera to a monitor
class ConnectCameraToMonitor extends CameraEvent {
final int cameraId;
final int monitorId;
const ConnectCameraToMonitor({
required this.cameraId,
required this.monitorId,
});
@override
List<Object?> get props => [cameraId, monitorId];
}
/// Clear selection
class ClearCameraSelection extends CameraEvent {
const ClearCameraSelection();
}

View File

@@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';
class CameraState extends Equatable {
final int? selectedCameraId;
final bool isConnecting;
final String? error;
final List<int> availableCameras;
const CameraState({
this.selectedCameraId,
this.isConnecting = false,
this.error,
this.availableCameras = const [],
});
bool get hasSelection => selectedCameraId != null;
CameraState copyWith({
int? selectedCameraId,
bool? isConnecting,
String? error,
List<int>? availableCameras,
bool clearSelection = false,
}) {
return CameraState(
selectedCameraId:
clearSelection ? null : (selectedCameraId ?? this.selectedCameraId),
isConnecting: isConnecting ?? this.isConnecting,
error: error,
availableCameras: availableCameras ?? this.availableCameras,
);
}
@override
List<Object?> get props =>
[selectedCameraId, isConnecting, error, availableCameras];
}