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:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user