Files
COPILOT/copilot_keyboard/lib/presentation/widgets/ptz_control.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

232 lines
7.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/ptz/ptz_bloc.dart';
import '../blocs/ptz/ptz_event.dart';
import '../blocs/ptz/ptz_state.dart';
import '../blocs/monitor/monitor_bloc.dart';
import '../blocs/monitor/monitor_state.dart';
class PtzControl extends StatelessWidget {
const PtzControl({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<MonitorBloc, MonitorBlocState>(
builder: (context, monitorState) {
return BlocBuilder<PtzBloc, PtzState>(
builder: (context, ptzState) {
final cameraId = monitorState.selectedMonitorCamera;
final isEnabled = cameraId != null && cameraId > 0;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Text(
'PTZ CONTROL',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
if (cameraId != null && cameraId > 0) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Camera $cameraId',
style: TextStyle(
fontSize: 12,
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
],
],
),
),
_buildPtzPad(context, cameraId, isEnabled),
const SizedBox(height: 8),
_buildZoomControls(context, cameraId, isEnabled),
],
);
},
);
},
);
}
Widget _buildPtzPad(BuildContext context, int? cameraId, bool isEnabled) {
return Column(
children: [
// Up
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_PtzButton(
icon: Icons.arrow_upward,
onPressStart: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(
PtzTiltStart(cameraId: cameraId, direction: 'up'),
)
: null,
onPressEnd: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(PtzStop(cameraId))
: null,
),
],
),
// Left, Stop, Right
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_PtzButton(
icon: Icons.arrow_back,
onPressStart: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(
PtzPanStart(cameraId: cameraId, direction: 'left'),
)
: null,
onPressEnd: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(PtzStop(cameraId))
: null,
),
const SizedBox(width: 4),
_PtzButton(
icon: Icons.stop,
isStop: true,
onPressStart: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(PtzStop(cameraId))
: null,
),
const SizedBox(width: 4),
_PtzButton(
icon: Icons.arrow_forward,
onPressStart: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(
PtzPanStart(cameraId: cameraId, direction: 'right'),
)
: null,
onPressEnd: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(PtzStop(cameraId))
: null,
),
],
),
// Down
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_PtzButton(
icon: Icons.arrow_downward,
onPressStart: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(
PtzTiltStart(cameraId: cameraId, direction: 'down'),
)
: null,
onPressEnd: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(PtzStop(cameraId))
: null,
),
],
),
],
);
}
Widget _buildZoomControls(
BuildContext context, int? cameraId, bool isEnabled) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_PtzButton(
icon: Icons.zoom_out,
onPressStart: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(
PtzZoomStart(cameraId: cameraId, direction: 'out'),
)
: null,
onPressEnd: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(PtzStop(cameraId))
: null,
),
const SizedBox(width: 4),
_PtzButton(
icon: Icons.zoom_in,
onPressStart: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(
PtzZoomStart(cameraId: cameraId, direction: 'in'),
)
: null,
onPressEnd: isEnabled && cameraId != null
? () => context.read<PtzBloc>().add(PtzStop(cameraId))
: null,
),
],
);
}
}
class _PtzButton extends StatelessWidget {
final IconData icon;
final VoidCallback? onPressStart;
final VoidCallback? onPressEnd;
final bool isStop;
const _PtzButton({
required this.icon,
this.onPressStart,
this.onPressEnd,
this.isStop = false,
});
@override
Widget build(BuildContext context) {
final isEnabled = onPressStart != null;
return GestureDetector(
onTapDown: isEnabled ? (_) => onPressStart?.call() : null,
onTapUp: isEnabled ? (_) => onPressEnd?.call() : null,
onTapCancel: isEnabled ? () => onPressEnd?.call() : null,
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: isEnabled
? (isStop
? Theme.of(context).colorScheme.errorContainer
: Theme.of(context).colorScheme.primaryContainer)
: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isEnabled
? (isStop
? Theme.of(context).colorScheme.error
: Theme.of(context).colorScheme.primary)
: Theme.of(context).colorScheme.outline,
width: 1,
),
),
child: Icon(
icon,
color: isEnabled
? (isStop
? Theme.of(context).colorScheme.onErrorContainer
: Theme.of(context).colorScheme.onPrimaryContainer)
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.4),
),
),
);
}
}