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,78 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../data/services/alarm_service.dart';
import '../../../data/services/state_service.dart';
import 'alarm_event.dart';
import 'alarm_state.dart';
class AlarmBloc extends Bloc<AlarmEvent, AlarmBlocState> {
final AlarmService _alarmService;
final StateService _stateService;
StreamSubscription? _alarmSubscription;
AlarmBloc({
required AlarmService alarmService,
required StateService stateService,
}) : _alarmService = alarmService,
_stateService = stateService,
super(const AlarmBlocState()) {
on<RefreshAlarms>(_onRefreshAlarms);
on<AlarmsUpdated>(_onAlarmsUpdated);
on<AcknowledgeAlarm>(_onAcknowledgeAlarm);
// Subscribe to alarm changes
_alarmSubscription = _alarmService.alarms.listen((alarms) {
add(AlarmsUpdated(alarms));
});
}
Future<void> _onRefreshAlarms(
RefreshAlarms event,
Emitter<AlarmBlocState> emit,
) async {
emit(state.copyWith(isLoading: true, error: null));
try {
await _alarmService.queryAllAlarms();
emit(state.copyWith(isLoading: false, lastSync: DateTime.now()));
} catch (e) {
emit(state.copyWith(
isLoading: false,
error: 'Failed to refresh alarms: $e',
));
}
}
void _onAlarmsUpdated(
AlarmsUpdated event,
Emitter<AlarmBlocState> emit,
) {
// Filter to only active alarms for display
final activeAlarms = event.alarms.where((a) => a.isActive).toList();
// Sort by start time (newest first)
activeAlarms.sort((a, b) => b.startedAt.compareTo(a.startedAt));
emit(state.copyWith(
activeAlarms: activeAlarms,
lastSync: DateTime.now(),
));
}
Future<void> _onAcknowledgeAlarm(
AcknowledgeAlarm event,
Emitter<AlarmBlocState> emit,
) async {
// Alarm acknowledgment would be implemented here
// This would call the bridge to acknowledge the alarm
// For now, just log that we received the event
}
@override
Future<void> close() {
_alarmSubscription?.cancel();
return super.close();
}
}

View File

@@ -0,0 +1,39 @@
import 'package:equatable/equatable.dart';
import '../../../domain/entities/alarm_state.dart';
abstract class AlarmEvent extends Equatable {
const AlarmEvent();
@override
List<Object?> get props => [];
}
/// Refresh alarms from server
class RefreshAlarms extends AlarmEvent {
const RefreshAlarms();
}
/// Alarms updated (internal)
class AlarmsUpdated extends AlarmEvent {
final List<AlarmState> alarms;
const AlarmsUpdated(this.alarms);
@override
List<Object?> get props => [alarms];
}
/// Acknowledge an alarm
class AcknowledgeAlarm extends AlarmEvent {
final int alarmId;
final String serverId;
const AcknowledgeAlarm({
required this.alarmId,
required this.serverId,
});
@override
List<Object?> get props => [alarmId, serverId];
}

View File

@@ -0,0 +1,51 @@
import 'package:equatable/equatable.dart';
import '../../../domain/entities/alarm_state.dart' as domain;
class AlarmBlocState extends Equatable {
final List<domain.AlarmState> activeAlarms;
final bool isLoading;
final String? error;
final DateTime? lastSync;
const AlarmBlocState({
this.activeAlarms = const [],
this.isLoading = false,
this.error,
this.lastSync,
});
/// Get count of active blocking alarms
int get blockingAlarmCount =>
activeAlarms.where((a) => a.blocksMonitor).length;
/// Get alarms for a specific monitor
List<domain.AlarmState> alarmsForMonitor(int monitorId) {
return activeAlarms
.where((a) => a.associatedMonitor == monitorId)
.toList();
}
/// Check if monitor has blocking alarm
bool monitorHasBlockingAlarm(int monitorId) {
return activeAlarms.any(
(a) => a.associatedMonitor == monitorId && a.blocksMonitor);
}
AlarmBlocState copyWith({
List<domain.AlarmState>? activeAlarms,
bool? isLoading,
String? error,
DateTime? lastSync,
}) {
return AlarmBlocState(
activeAlarms: activeAlarms ?? this.activeAlarms,
isLoading: isLoading ?? this.isLoading,
error: error,
lastSync: lastSync ?? this.lastSync,
);
}
@override
List<Object?> get props => [activeAlarms, isLoading, error, lastSync];
}