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,72 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../config/app_config.dart';
import '../../../data/services/state_service.dart';
import '../../../data/services/bridge_service.dart';
import '../../../injection_container.dart';
import 'monitor_event.dart';
import 'monitor_state.dart';
class MonitorBloc extends Bloc<MonitorEvent, MonitorBlocState> {
final StateService _stateService;
final AppConfig _config;
StreamSubscription? _stateSubscription;
MonitorBloc({
required StateService stateService,
required AppConfig config,
}) : _stateService = stateService,
_config = config,
super(MonitorBlocState(availableMonitors: config.allMonitorIds)) {
on<SelectMonitor>(_onSelectMonitor);
on<ClearMonitor>(_onClearMonitor);
on<ClearMonitorSelection>(_onClearMonitorSelection);
on<MonitorStatesUpdated>(_onMonitorStatesUpdated);
// Subscribe to monitor state changes
_stateSubscription = _stateService.combinedMonitorStates.listen((states) {
add(MonitorStatesUpdated(states));
});
}
void _onSelectMonitor(
SelectMonitor event,
Emitter<MonitorBlocState> emit,
) {
emit(state.copyWith(selectedMonitorId: event.monitorId, error: null));
}
Future<void> _onClearMonitor(
ClearMonitor event,
Emitter<MonitorBlocState> emit,
) async {
try {
final bridgeService = sl<BridgeService>();
await bridgeService.viewerClear(event.monitorId);
} catch (e) {
emit(state.copyWith(error: 'Failed to clear monitor: $e'));
}
}
void _onClearMonitorSelection(
ClearMonitorSelection event,
Emitter<MonitorBlocState> emit,
) {
emit(state.copyWith(clearSelection: true, error: null));
}
void _onMonitorStatesUpdated(
MonitorStatesUpdated event,
Emitter<MonitorBlocState> emit,
) {
emit(state.copyWith(monitorStates: event.states));
}
@override
Future<void> close() {
_stateSubscription?.cancel();
return super.close();
}
}

View File

@@ -0,0 +1,45 @@
import 'package:equatable/equatable.dart';
import '../../../domain/entities/monitor_state.dart';
abstract class MonitorEvent extends Equatable {
const MonitorEvent();
@override
List<Object?> get props => [];
}
/// Select a monitor for camera switching
class SelectMonitor extends MonitorEvent {
final int monitorId;
const SelectMonitor(this.monitorId);
@override
List<Object?> get props => [monitorId];
}
/// Clear the selected monitor
class ClearMonitor extends MonitorEvent {
final int monitorId;
const ClearMonitor(this.monitorId);
@override
List<Object?> get props => [monitorId];
}
/// Clear selection
class ClearMonitorSelection extends MonitorEvent {
const ClearMonitorSelection();
}
/// Monitor states updated (internal)
class MonitorStatesUpdated extends MonitorEvent {
final Map<int, MonitorState> states;
const MonitorStatesUpdated(this.states);
@override
List<Object?> get props => [states];
}

View File

@@ -0,0 +1,50 @@
import 'package:equatable/equatable.dart';
import '../../../domain/entities/monitor_state.dart' as domain;
class MonitorBlocState extends Equatable {
final int? selectedMonitorId;
final Map<int, domain.MonitorState> monitorStates;
final List<int> availableMonitors;
final String? error;
const MonitorBlocState({
this.selectedMonitorId,
this.monitorStates = const {},
this.availableMonitors = const [],
this.error,
});
bool get hasSelection => selectedMonitorId != null;
/// Get the currently selected monitor's state
domain.MonitorState? get selectedMonitorState {
if (selectedMonitorId == null) return null;
return monitorStates[selectedMonitorId];
}
/// Get the camera currently on the selected monitor
int? get selectedMonitorCamera {
return selectedMonitorState?.currentChannel;
}
MonitorBlocState copyWith({
int? selectedMonitorId,
Map<int, domain.MonitorState>? monitorStates,
List<int>? availableMonitors,
String? error,
bool clearSelection = false,
}) {
return MonitorBlocState(
selectedMonitorId:
clearSelection ? null : (selectedMonitorId ?? this.selectedMonitorId),
monitorStates: monitorStates ?? this.monitorStates,
availableMonitors: availableMonitors ?? this.availableMonitors,
error: error,
);
}
@override
List<Object?> get props =>
[selectedMonitorId, monitorStates, availableMonitors, error];
}