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,129 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/camera/camera_bloc.dart';
import '../blocs/camera/camera_event.dart';
import '../blocs/camera/camera_state.dart';
import '../blocs/monitor/monitor_bloc.dart';
import '../blocs/monitor/monitor_state.dart';
class CameraGrid extends StatelessWidget {
final int columns;
const CameraGrid({super.key, this.columns = 8});
@override
Widget build(BuildContext context) {
return BlocBuilder<CameraBloc, CameraState>(
builder: (context, cameraState) {
return BlocBuilder<MonitorBloc, MonitorBlocState>(
builder: (context, monitorState) {
final cameras = cameraState.availableCameras;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'CAMERAS',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Wrap(
spacing: 4,
runSpacing: 4,
children: cameras.map((cameraId) {
final isSelected =
cameraState.selectedCameraId == cameraId;
final isOnSelectedMonitor =
monitorState.selectedMonitorCamera == cameraId;
return _CameraButton(
cameraId: cameraId,
isSelected: isSelected,
isOnSelectedMonitor: isOnSelectedMonitor,
onPressed: () => _onCameraPressed(
context,
cameraId,
monitorState.selectedMonitorId,
),
);
}).toList(),
),
],
);
},
);
},
);
}
void _onCameraPressed(BuildContext context, int cameraId, int? monitorId) {
final cameraBloc = context.read<CameraBloc>();
if (monitorId != null) {
// Monitor is selected, connect camera to it
cameraBloc.add(ConnectCameraToMonitor(
cameraId: cameraId,
monitorId: monitorId,
));
} else {
// Just select the camera
cameraBloc.add(SelectCamera(cameraId));
}
}
}
class _CameraButton extends StatelessWidget {
final int cameraId;
final bool isSelected;
final bool isOnSelectedMonitor;
final VoidCallback onPressed;
const _CameraButton({
required this.cameraId,
required this.isSelected,
required this.isOnSelectedMonitor,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
Color backgroundColor;
Color foregroundColor;
if (isSelected) {
backgroundColor = Theme.of(context).colorScheme.primary;
foregroundColor = Theme.of(context).colorScheme.onPrimary;
} else if (isOnSelectedMonitor) {
backgroundColor = Theme.of(context).colorScheme.secondary;
foregroundColor = Theme.of(context).colorScheme.onSecondary;
} else {
backgroundColor = Theme.of(context).colorScheme.surfaceContainerHighest;
foregroundColor = Theme.of(context).colorScheme.onSurface;
}
return SizedBox(
width: 48,
height: 40,
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
child: Text(
'$cameraId',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
),
);
}
}