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,137 @@
import 'package:equatable/equatable.dart';
/// Alarm state enumeration matching SDK PlcViewerAlarmState
enum AlarmStatus {
newAlarm(0, 'vasNewAlarm'),
presented(1, 'vasPresented'),
stacked(2, 'vasStacked'),
confirmed(3, 'vasConfirmed'),
removed(4, 'vasRemoved'),
lastConfirmed(5, 'vasLastConfirmed'),
lastRemoved(6, 'vasLastRemoved');
final int value;
final String name;
const AlarmStatus(this.value, this.name);
static AlarmStatus fromValue(int value) {
return AlarmStatus.values.firstWhere(
(s) => s.value == value,
orElse: () => AlarmStatus.newAlarm,
);
}
/// Check if this status blocks the monitor
bool get blocksMonitor =>
this == AlarmStatus.newAlarm || this == AlarmStatus.presented;
}
/// State of a single alarm/event
class AlarmState extends Equatable {
final int eventId;
final String eventName;
final int typeId;
final int foreignKey; // Camera or contact ID
final String? serverId;
final DateTime startedAt;
final DateTime? stoppedAt;
final bool isActive;
final AlarmStatus status;
final int? associatedMonitor;
const AlarmState({
required this.eventId,
required this.eventName,
required this.typeId,
required this.foreignKey,
this.serverId,
required this.startedAt,
this.stoppedAt,
required this.isActive,
this.status = AlarmStatus.newAlarm,
this.associatedMonitor,
});
/// Check if this alarm blocks a monitor
bool get blocksMonitor => isActive && status.blocksMonitor;
/// Create a stopped alarm
AlarmState stopped() {
return AlarmState(
eventId: eventId,
eventName: eventName,
typeId: typeId,
foreignKey: foreignKey,
serverId: serverId,
startedAt: startedAt,
stoppedAt: DateTime.now(),
isActive: false,
status: AlarmStatus.removed,
associatedMonitor: associatedMonitor,
);
}
/// Create alarm with updated status
AlarmState withStatus(AlarmStatus newStatus) {
return AlarmState(
eventId: eventId,
eventName: eventName,
typeId: typeId,
foreignKey: foreignKey,
serverId: serverId,
startedAt: startedAt,
stoppedAt: stoppedAt,
isActive: isActive,
status: newStatus,
associatedMonitor: associatedMonitor,
);
}
factory AlarmState.fromJson(Map<String, dynamic> json) {
return AlarmState(
eventId: json['event_id'] as int? ?? 0,
eventName: json['event_name'] as String? ?? '',
typeId: json['type_id'] as int? ?? 0,
foreignKey: json['foreign_key'] as int? ?? 0,
serverId: json['server_id'] as String?,
startedAt: json['started_at'] != null
? DateTime.parse(json['started_at'] as String)
: DateTime.now(),
stoppedAt: json['stopped_at'] != null
? DateTime.parse(json['stopped_at'] as String)
: null,
isActive: json['is_active'] as bool? ?? true,
status: AlarmStatus.fromValue(json['status'] as int? ?? 0),
associatedMonitor: json['associated_monitor'] as int?,
);
}
Map<String, dynamic> toJson() {
return {
'event_id': eventId,
'event_name': eventName,
'type_id': typeId,
'foreign_key': foreignKey,
'server_id': serverId,
'started_at': startedAt.toIso8601String(),
'stopped_at': stoppedAt?.toIso8601String(),
'is_active': isActive,
'status': status.value,
'associated_monitor': associatedMonitor,
};
}
@override
List<Object?> get props => [
eventId,
eventName,
typeId,
foreignKey,
serverId,
startedAt,
stoppedAt,
isActive,
status,
associatedMonitor,
];
}