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