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,90 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../data/services/coordination_service.dart';
|
||||
import '../../../domain/entities/sequence.dart';
|
||||
import 'sequence_event.dart';
|
||||
import 'sequence_state.dart';
|
||||
|
||||
class SequenceBloc extends Bloc<SequenceEvent, SequenceState> {
|
||||
final CoordinationService _coordinationService;
|
||||
|
||||
SequenceBloc({required CoordinationService coordinationService})
|
||||
: _coordinationService = coordinationService,
|
||||
super(const SequenceState()) {
|
||||
on<LoadSequences>(_onLoadSequences);
|
||||
on<StartSequence>(_onStartSequence);
|
||||
on<StopSequence>(_onStopSequence);
|
||||
on<SelectCategory>(_onSelectCategory);
|
||||
}
|
||||
|
||||
Future<void> _onLoadSequences(
|
||||
LoadSequences event, Emitter<SequenceState> emit) async {
|
||||
emit(state.copyWith(isLoading: true, clearError: true));
|
||||
|
||||
try {
|
||||
final sequencesJson = await _coordinationService.getSequences();
|
||||
final categoriesJson = await _coordinationService.getSequenceCategories();
|
||||
final runningJson = await _coordinationService.getRunningSequences();
|
||||
|
||||
final sequences = sequencesJson
|
||||
.map((j) => SequenceDefinition.fromJson(j))
|
||||
.toList();
|
||||
final categories =
|
||||
categoriesJson.map((j) => SequenceCategory.fromJson(j)).toList();
|
||||
|
||||
final running = <int, RunningSequence>{};
|
||||
for (final j in runningJson) {
|
||||
final rs = RunningSequence.fromJson(j);
|
||||
running[rs.viewerId] = rs;
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
sequences: sequences,
|
||||
categories: categories,
|
||||
running: running,
|
||||
isLoading: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isLoading: false, error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onStartSequence(
|
||||
StartSequence event, Emitter<SequenceState> emit) async {
|
||||
try {
|
||||
final result = await _coordinationService.startSequence(
|
||||
event.viewerId, event.sequenceId);
|
||||
|
||||
if (result != null) {
|
||||
final rs = RunningSequence.fromJson(result);
|
||||
final running = Map<int, RunningSequence>.from(state.running);
|
||||
running[rs.viewerId] = rs;
|
||||
emit(state.copyWith(running: running));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(state.copyWith(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onStopSequence(
|
||||
StopSequence event, Emitter<SequenceState> emit) async {
|
||||
try {
|
||||
await _coordinationService.stopSequence(event.viewerId);
|
||||
|
||||
final running = Map<int, RunningSequence>.from(state.running);
|
||||
running.remove(event.viewerId);
|
||||
emit(state.copyWith(running: running));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectCategory(
|
||||
SelectCategory event, Emitter<SequenceState> emit) {
|
||||
if (event.categoryId == null) {
|
||||
emit(state.copyWith(clearCategory: true));
|
||||
} else {
|
||||
emit(state.copyWith(selectedCategoryId: event.categoryId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
abstract class SequenceEvent {}
|
||||
|
||||
/// Load available sequences and categories from coordinator
|
||||
class LoadSequences extends SequenceEvent {}
|
||||
|
||||
/// Start a sequence on a viewer
|
||||
class StartSequence extends SequenceEvent {
|
||||
final int viewerId;
|
||||
final int sequenceId;
|
||||
|
||||
StartSequence({required this.viewerId, required this.sequenceId});
|
||||
}
|
||||
|
||||
/// Stop a sequence on a viewer
|
||||
class StopSequence extends SequenceEvent {
|
||||
final int viewerId;
|
||||
|
||||
StopSequence(this.viewerId);
|
||||
}
|
||||
|
||||
/// Filter sequences by category
|
||||
class SelectCategory extends SequenceEvent {
|
||||
final int? categoryId;
|
||||
|
||||
SelectCategory(this.categoryId);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import '../../../domain/entities/sequence.dart';
|
||||
|
||||
class SequenceState {
|
||||
final List<SequenceDefinition> sequences;
|
||||
final List<SequenceCategory> categories;
|
||||
final Map<int, RunningSequence> running; // viewerId -> RunningSequence
|
||||
final int? selectedCategoryId;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
|
||||
const SequenceState({
|
||||
this.sequences = const [],
|
||||
this.categories = const [],
|
||||
this.running = const {},
|
||||
this.selectedCategoryId,
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
/// Sequences filtered by selected category
|
||||
List<SequenceDefinition> get filteredSequences {
|
||||
if (selectedCategoryId == null) return sequences;
|
||||
return sequences
|
||||
.where((s) => s.categoryId == selectedCategoryId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Check if a sequence is running on a viewer
|
||||
bool isRunningOnViewer(int viewerId) => running.containsKey(viewerId);
|
||||
|
||||
/// Get running sequence for a viewer
|
||||
RunningSequence? getRunning(int viewerId) => running[viewerId];
|
||||
|
||||
SequenceState copyWith({
|
||||
List<SequenceDefinition>? sequences,
|
||||
List<SequenceCategory>? categories,
|
||||
Map<int, RunningSequence>? running,
|
||||
int? selectedCategoryId,
|
||||
bool clearCategory = false,
|
||||
bool? isLoading,
|
||||
String? error,
|
||||
bool clearError = false,
|
||||
}) {
|
||||
return SequenceState(
|
||||
sequences: sequences ?? this.sequences,
|
||||
categories: categories ?? this.categories,
|
||||
running: running ?? this.running,
|
||||
selectedCategoryId: clearCategory
|
||||
? null
|
||||
: (selectedCategoryId ?? this.selectedCategoryId),
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
error: clearError ? null : (error ?? this.error),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user