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,120 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../config/app_config.dart';
import '../../../data/services/bridge_service.dart';
import 'connection_event.dart';
import 'connection_state.dart';
class ConnectionBloc extends Bloc<ConnectionEvent, ConnectionState> {
final BridgeService _bridgeService;
final AppConfig _config;
StreamSubscription? _statusSubscription;
ConnectionBloc({
required BridgeService bridgeService,
required AppConfig config,
}) : _bridgeService = bridgeService,
_config = config,
super(const ConnectionState()) {
on<ConnectAll>(_onConnectAll);
on<ConnectServer>(_onConnectServer);
on<DisconnectServer>(_onDisconnectServer);
on<DisconnectAll>(_onDisconnectAll);
on<RetryConnections>(_onRetryConnections);
on<ConnectionStatusUpdated>(_onConnectionStatusUpdated);
// Subscribe to connection status changes
_statusSubscription = _bridgeService.connectionStatus.listen((status) {
add(ConnectionStatusUpdated(status));
});
}
Future<void> _onConnectAll(
ConnectAll event,
Emitter<ConnectionState> emit,
) async {
emit(state.copyWith(overallStatus: ConnectionOverallStatus.connecting));
try {
await _bridgeService.connectAll();
} catch (e) {
emit(state.copyWith(
overallStatus: ConnectionOverallStatus.disconnected,
error: e.toString(),
));
}
}
Future<void> _onConnectServer(
ConnectServer event,
Emitter<ConnectionState> emit,
) async {
try {
await _bridgeService.connect(event.serverId);
} catch (e) {
emit(state.copyWith(error: 'Failed to connect to ${event.serverId}: $e'));
}
}
Future<void> _onDisconnectServer(
DisconnectServer event,
Emitter<ConnectionState> emit,
) async {
await _bridgeService.disconnect(event.serverId);
}
Future<void> _onDisconnectAll(
DisconnectAll event,
Emitter<ConnectionState> emit,
) async {
await _bridgeService.disconnectAll();
emit(state.copyWith(overallStatus: ConnectionOverallStatus.disconnected));
}
Future<void> _onRetryConnections(
RetryConnections event,
Emitter<ConnectionState> emit,
) async {
// Retry only disconnected servers
final disconnected = state.serverStatus.entries
.where((e) => !e.value)
.map((e) => e.key)
.toList();
for (final serverId in disconnected) {
await _bridgeService.connect(serverId);
}
}
void _onConnectionStatusUpdated(
ConnectionStatusUpdated event,
Emitter<ConnectionState> emit,
) {
final status = event.status;
ConnectionOverallStatus overall;
if (status.isEmpty) {
overall = ConnectionOverallStatus.disconnected;
} else if (status.values.every((v) => v)) {
overall = ConnectionOverallStatus.connected;
} else if (status.values.any((v) => v)) {
overall = ConnectionOverallStatus.partial;
} else {
overall = ConnectionOverallStatus.disconnected;
}
emit(state.copyWith(
overallStatus: overall,
serverStatus: status,
error: null,
));
}
@override
Future<void> close() {
_statusSubscription?.cancel();
return super.close();
}
}

View File

@@ -0,0 +1,53 @@
import 'package:equatable/equatable.dart';
abstract class ConnectionEvent extends Equatable {
const ConnectionEvent();
@override
List<Object?> get props => [];
}
/// Connect to all servers
class ConnectAll extends ConnectionEvent {
const ConnectAll();
}
/// Connect to a specific server
class ConnectServer extends ConnectionEvent {
final String serverId;
const ConnectServer(this.serverId);
@override
List<Object?> get props => [serverId];
}
/// Disconnect from a specific server
class DisconnectServer extends ConnectionEvent {
final String serverId;
const DisconnectServer(this.serverId);
@override
List<Object?> get props => [serverId];
}
/// Disconnect from all servers
class DisconnectAll extends ConnectionEvent {
const DisconnectAll();
}
/// Retry failed connections
class RetryConnections extends ConnectionEvent {
const RetryConnections();
}
/// Connection status updated (internal)
class ConnectionStatusUpdated extends ConnectionEvent {
final Map<String, bool> status;
const ConnectionStatusUpdated(this.status);
@override
List<Object?> get props => [status];
}

View File

@@ -0,0 +1,43 @@
import 'package:equatable/equatable.dart';
enum ConnectionOverallStatus { disconnected, connecting, connected, partial }
class ConnectionState extends Equatable {
final ConnectionOverallStatus overallStatus;
final Map<String, bool> serverStatus;
final String? error;
const ConnectionState({
this.overallStatus = ConnectionOverallStatus.disconnected,
this.serverStatus = const {},
this.error,
});
/// Check if all servers are connected
bool get allConnected =>
serverStatus.isNotEmpty && serverStatus.values.every((v) => v);
/// Check if any server is connected
bool get anyConnected => serverStatus.values.any((v) => v);
/// Get count of connected servers
int get connectedCount => serverStatus.values.where((v) => v).length;
/// Get count of total servers
int get totalCount => serverStatus.length;
ConnectionState copyWith({
ConnectionOverallStatus? overallStatus,
Map<String, bool>? serverStatus,
String? error,
}) {
return ConnectionState(
overallStatus: overallStatus ?? this.overallStatus,
serverStatus: serverStatus ?? this.serverStatus,
error: error,
);
}
@override
List<Object?> get props => [overallStatus, serverStatus, error];
}