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