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,179 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:logger/logger.dart';
import '../domain/entities/function_button_config.dart';
import '../domain/entities/server_config.dart';
/// Application configuration loaded from servers.json
class AppConfig {
final List<ServerConfig> servers;
final String coordinatorUrl;
final String keyboardId;
final FunctionButtonConfig functionButtons;
final int alarmSyncIntervalSeconds;
final int connectionRetrySeconds;
final int commandTimeoutSeconds;
const AppConfig({
required this.servers,
this.coordinatorUrl = 'http://localhost:8090',
this.keyboardId = 'keyboard-1',
this.functionButtons = const FunctionButtonConfig(),
this.alarmSyncIntervalSeconds = 30,
this.connectionRetrySeconds = 5,
this.commandTimeoutSeconds = 10,
});
/// Load configuration from file or assets
static Future<AppConfig> load({String? configPath}) async {
final logger = Logger();
Map<String, dynamic> configJson;
// Try loading from file path first
if (configPath != null) {
try {
final file = File(configPath);
if (await file.exists()) {
final contents = await file.readAsString();
configJson = jsonDecode(contents) as Map<String, dynamic>;
logger.i('Loaded config from: $configPath');
return _parseConfig(configJson);
}
} catch (e) {
logger.w('Failed to load config from file: $e');
}
}
// Try common file locations
final commonPaths = [
'servers.json',
'../servers.json',
'config/servers.json',
r'C:\DEV\COPILOT_D6\servers.json',
];
for (final path in commonPaths) {
try {
final file = File(path);
if (await file.exists()) {
final contents = await file.readAsString();
configJson = jsonDecode(contents) as Map<String, dynamic>;
logger.i('Loaded config from: $path');
return _parseConfig(configJson);
}
} catch (e) {
// Continue to next path
}
}
// Try loading from assets
try {
final contents = await rootBundle.loadString('assets/config/servers.json');
configJson = jsonDecode(contents) as Map<String, dynamic>;
logger.i('Loaded config from assets');
return _parseConfig(configJson);
} catch (e) {
logger.w('Failed to load config from assets: $e');
}
// Return default empty config
logger.w('No config file found, using defaults');
return const AppConfig(servers: []);
}
static AppConfig _parseConfig(Map<String, dynamic> json) {
final serversJson = json['servers'] as List<dynamic>? ?? [];
final servers = serversJson
.map((s) => ServerConfig.fromJson(s as Map<String, dynamic>))
.where((s) => s.enabled)
.toList();
final settings = json['settings'] as Map<String, dynamic>? ?? {};
// Parse function button config
final fbJson = json['functionButtons'] as Map<String, dynamic>?;
final functionButtons = fbJson != null
? FunctionButtonConfig.fromJson(fbJson)
: const FunctionButtonConfig();
return AppConfig(
servers: servers,
coordinatorUrl: settings['coordinatorUrl'] as String? ?? 'http://localhost:8090',
keyboardId: settings['keyboardId'] as String? ?? 'keyboard-1',
functionButtons: functionButtons,
alarmSyncIntervalSeconds: settings['alarmSyncIntervalSeconds'] as int? ?? 30,
connectionRetrySeconds: settings['connectionRetrySeconds'] as int? ?? 5,
commandTimeoutSeconds: settings['commandTimeoutSeconds'] as int? ?? 10,
);
}
/// Get servers by type
List<ServerConfig> getServersByType(ServerType type) {
return servers.where((s) => s.type == type).toList();
}
/// Get server that owns a camera ID
ServerConfig? getServerForCamera(int cameraId) {
for (final server in servers) {
if (server.ownsCamera(cameraId)) {
return server;
}
}
return null;
}
/// Get server that owns a monitor ID
ServerConfig? getServerForMonitor(int monitorId) {
for (final server in servers) {
if (server.ownsMonitor(monitorId)) {
return server;
}
}
return null;
}
/// Get total camera count across all servers
int get totalCameras {
int count = 0;
for (final server in servers) {
count += (server.cameraRangeEnd - server.cameraRangeStart + 1);
}
return count;
}
/// Get total monitor count across all servers
int get totalMonitors {
int count = 0;
for (final server in servers) {
count += (server.monitorRangeEnd - server.monitorRangeStart + 1);
}
return count;
}
/// Get all camera IDs
List<int> get allCameraIds {
final ids = <int>[];
for (final server in servers) {
for (int i = server.cameraRangeStart; i <= server.cameraRangeEnd; i++) {
ids.add(i);
}
}
ids.sort();
return ids;
}
/// Get all monitor IDs
List<int> get allMonitorIds {
final ids = <int>[];
for (final server in servers) {
for (int i = server.monitorRangeStart; i <= server.monitorRangeEnd; i++) {
ids.add(i);
}
}
ids.sort();
return ids;
}
}