Files
COPILOT/copilot_keyboard/lib/presentation/widgets/monitor_grid.dart
klas 40143734fc 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>
2026-02-12 14:57:38 +01:00

151 lines
4.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../domain/entities/monitor_state.dart';
import '../blocs/monitor/monitor_bloc.dart';
import '../blocs/monitor/monitor_event.dart';
import '../blocs/monitor/monitor_state.dart';
class MonitorGrid extends StatelessWidget {
final int columns;
const MonitorGrid({super.key, this.columns = 4});
@override
Widget build(BuildContext context) {
return BlocBuilder<MonitorBloc, MonitorBlocState>(
builder: (context, state) {
final monitors = state.availableMonitors;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'MONITORS',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Wrap(
spacing: 4,
runSpacing: 4,
children: monitors.map((monitorId) {
final isSelected = state.selectedMonitorId == monitorId;
final monitorState = state.monitorStates[monitorId];
return _MonitorButton(
monitorId: monitorId,
isSelected: isSelected,
monitorState: monitorState,
onPressed: () => _onMonitorPressed(context, monitorId),
onLongPress: () => _onMonitorLongPress(context, monitorId),
);
}).toList(),
),
],
);
},
);
}
void _onMonitorPressed(BuildContext context, int monitorId) {
context.read<MonitorBloc>().add(SelectMonitor(monitorId));
}
void _onMonitorLongPress(BuildContext context, int monitorId) {
context.read<MonitorBloc>().add(ClearMonitor(monitorId));
}
}
class _MonitorButton extends StatelessWidget {
final int monitorId;
final bool isSelected;
final MonitorState? monitorState;
final VoidCallback onPressed;
final VoidCallback onLongPress;
const _MonitorButton({
required this.monitorId,
required this.isSelected,
this.monitorState,
required this.onPressed,
required this.onLongPress,
});
@override
Widget build(BuildContext context) {
final hasAlarm = monitorState?.hasAlarm ?? false;
final currentCamera = monitorState?.currentChannel ?? 0;
final isActive = currentCamera > 0;
Color backgroundColor;
Color foregroundColor;
Color? borderColor;
if (hasAlarm) {
backgroundColor = Colors.red.shade700;
foregroundColor = Colors.white;
borderColor = Colors.red.shade900;
} else if (isSelected) {
backgroundColor = Theme.of(context).colorScheme.primary;
foregroundColor = Theme.of(context).colorScheme.onPrimary;
} else if (isActive) {
backgroundColor = Theme.of(context).colorScheme.primaryContainer;
foregroundColor = Theme.of(context).colorScheme.onPrimaryContainer;
} else {
backgroundColor = Theme.of(context).colorScheme.surfaceContainerHighest;
foregroundColor = Theme.of(context).colorScheme.onSurface;
}
return SizedBox(
width: 64,
height: 48,
child: GestureDetector(
onLongPress: onLongPress,
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
padding: const EdgeInsets.all(4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: borderColor != null
? BorderSide(color: borderColor, width: 2)
: BorderSide.none,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (hasAlarm)
const Icon(Icons.warning, size: 12, color: Colors.yellow),
Text(
'$monitorId',
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold),
),
],
),
if (isActive)
Text(
'C$currentCamera',
style: TextStyle(
fontSize: 10,
color: foregroundColor.withValues(alpha: 0.8),
),
),
],
),
),
),
);
}
}