Files
Administrator 14893e62a5 feat: Geutebruck GeViScope/GeViSoft Action Mapping System - MVP
This MVP release provides a complete full-stack solution for managing action mappings
in Geutebruck's GeViScope and GeViSoft video surveillance systems.

## Features

### Flutter Web Application (Port 8081)
- Modern, responsive UI for managing action mappings
- Action picker dialog with full parameter configuration
- Support for both GSC (GeViScope) and G-Core server actions
- Consistent UI for input and output actions with edit/delete capabilities
- Real-time action mapping creation, editing, and deletion
- Server categorization (GSC: prefix for GeViScope, G-Core: prefix for G-Core servers)

### FastAPI REST Backend (Port 8000)
- RESTful API for action mapping CRUD operations
- Action template service with comprehensive action catalog (247 actions)
- Server management (G-Core and GeViScope servers)
- Configuration tree reading and writing
- JWT authentication with role-based access control
- PostgreSQL database integration

### C# SDK Bridge (gRPC, Port 50051)
- Native integration with GeViSoft SDK (GeViProcAPINET_4_0.dll)
- Action mapping creation with correct binary format
- Support for GSC and G-Core action types
- Proper Camera parameter inclusion in action strings (fixes CrossSwitch bug)
- Action ID lookup table with server-specific action IDs
- Configuration reading/writing via SetupClient

## Bug Fixes
- **CrossSwitch Bug**: GSC and G-Core actions now correctly display camera/PTZ head parameters in GeViSet
- Action strings now include Camera parameter: `@ PanLeft (Comment: "", Camera: 101028)`
- Proper filter flags and VideoInput=0 for action mappings
- Correct action ID assignment (4198 for GSC, 9294 for G-Core PanLeft)

## Technical Stack
- **Frontend**: Flutter Web, Dart, Dio HTTP client
- **Backend**: Python FastAPI, PostgreSQL, Redis
- **SDK Bridge**: C# .NET 8.0, gRPC, GeViSoft SDK
- **Authentication**: JWT tokens
- **Configuration**: GeViSoft .set files (binary format)

## Credentials
- GeViSoft/GeViScope: username=sysadmin, password=masterkey
- Default admin: username=admin, password=admin123

## Deployment
All services run on localhost:
- Flutter Web: http://localhost:8081
- FastAPI: http://localhost:8000
- SDK Bridge gRPC: localhost:50051
- GeViServer: localhost (default port)

Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-31 18:10:54 +01:00

619 lines
18 KiB
Markdown

# Geutebruck API Flutter App - Technical Implementation Plan
## Technology Stack
### Core Framework
- **Flutter 3.24+**: Cross-platform mobile framework
- **Dart 3.0+**: Programming language with sound null safety
### State Management
- **flutter_bloc 8.1+**: BLoC pattern implementation
- *Rationale*: Predictable state management, excellent testing support, separation of concerns
- *Alternatives considered*: Provider (simpler but less structured), Riverpod (newer but less mature ecosystem)
### HTTP Client & API
- **dio 5.4+**: HTTP client for API calls
- Features: Interceptors, request/response transformation, timeout handling
- Better than http package: More features, better error handling
- **retrofit 4.0+**: Type-safe HTTP client generator
- Auto-generates API client code from interface definitions
- Reduces boilerplate and errors
### Data Persistence
- **flutter_secure_storage 9.0+**: Secure storage for tokens and credentials
- **shared_preferences 2.2+**: App settings and preferences
- **hive 2.2+**: Local database for caching
- *Rationale*: Fast, lightweight, no SQL required, perfect for caching API responses
### Dependency Injection
- **get_it 7.6+**: Service locator pattern
- *Rationale*: Simple, explicit dependencies, excellent for testing
- **injectable 2.3+**: Code generation for get_it
- Reduces boilerplate in dependency registration
### UI Components
- **Material Design 3**: Modern, accessible UI components
- **cached_network_image 3.3+**: Efficient image loading and caching
- **shimmer 3.0+**: Loading skeletons
- **flutter_svg 2.0+**: SVG image support
### Navigation
- **go_router 13.0+**: Declarative routing
- *Rationale*: Deep linking support, type-safe navigation, excellent for web
### Form Handling & Validation
- **flutter_form_builder 9.1+**: Dynamic form creation
- **form_builder_validators 9.1+**: Reusable validators
### Testing
- **mockito 5.4+**: Mocking dependencies
- **bloc_test 9.1+**: Testing BLoCs
- **golden_toolkit 0.15+**: Widget screenshot testing
### Code Generation
- **freezed 2.4+**: Immutable data classes with union types
- **json_serializable 6.7+**: JSON serialization
- **build_runner 2.4+**: Code generation orchestrator
### Development Tools
- **flutter_launcher_icons 0.13+**: App icon generation
- **flutter_native_splash 2.3+**: Splash screen generation
- **very_good_analysis 5.1+**: Lint rules
## Architecture
### Clean Architecture + BLoC
```
┌─────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Screens │ │ Widgets │ │ BLoCs │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Domain Layer │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Entities │ │ Use Cases │ │ Repository │ │
│ │ │ │ │ │ Interfaces │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Data Layer │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Models │ │Repositories│ │Data Sources│ │
│ │ │ │ │ │ (Remote) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────┘
```
### Layer Responsibilities
#### Presentation Layer
- **Screens**: Full-page views (ServerListScreen, LoginScreen, etc.)
- **Widgets**: Reusable UI components (ServerCard, LoadingWidget, etc.)
- **BLoCs**: Business logic controllers, emit states based on events
#### Domain Layer
- **Entities**: Pure business objects (Server, ActionMapping, Camera)
- **Use Cases**: Single-responsibility business operations (GetServers, CreateServer)
- **Repository Interfaces**: Contracts for data access
#### Data Layer
- **Models**: Data transfer objects with JSON serialization
- **Repositories**: Implement repository interfaces, coordinate data sources
- **Data Sources**:
- **Remote**: API client using dio/retrofit
- **Local**: Hive cache
### Folder Structure
```
lib/
├── core/
│ ├── constants/
│ │ ├── api_constants.dart
│ │ ├── app_constants.dart
│ │ └── asset_constants.dart
│ ├── errors/
│ │ ├── exceptions.dart
│ │ └── failures.dart
│ ├── network/
│ │ ├── api_client.dart
│ │ ├── dio_client.dart
│ │ └── interceptors/
│ │ ├── auth_interceptor.dart
│ │ └── logging_interceptor.dart
│ ├── theme/
│ │ ├── app_theme.dart
│ │ └── colors.dart
│ └── utils/
│ ├── validators.dart
│ └── extensions.dart
├── data/
│ ├── models/
│ │ ├── server_model.dart
│ │ ├── action_mapping_model.dart
│ │ ├── camera_model.dart
│ │ └── auth_model.dart
│ ├── repositories/
│ │ ├── server_repository_impl.dart
│ │ ├── action_mapping_repository_impl.dart
│ │ ├── camera_repository_impl.dart
│ │ └── auth_repository_impl.dart
│ └── data_sources/
│ ├── remote/
│ │ ├── server_remote_data_source.dart
│ │ ├── action_mapping_remote_data_source.dart
│ │ └── auth_remote_data_source.dart
│ └── local/
│ ├── cache_manager.dart
│ └── secure_storage_manager.dart
├── domain/
│ ├── entities/
│ │ ├── server.dart
│ │ ├── action_mapping.dart
│ │ ├── camera.dart
│ │ └── user.dart
│ ├── repositories/
│ │ ├── server_repository.dart
│ │ ├── action_mapping_repository.dart
│ │ └── auth_repository.dart
│ └── use_cases/
│ ├── servers/
│ │ ├── get_servers.dart
│ │ ├── create_server.dart
│ │ ├── update_server.dart
│ │ └── delete_server.dart
│ ├── action_mappings/
│ │ ├── get_action_mappings.dart
│ │ └── create_action_mapping.dart
│ └── auth/
│ ├── login.dart
│ ├── refresh_token.dart
│ └── logout.dart
├── presentation/
│ ├── blocs/
│ │ ├── auth/
│ │ │ ├── auth_bloc.dart
│ │ │ ├── auth_event.dart
│ │ │ └── auth_state.dart
│ │ ├── server/
│ │ │ ├── server_bloc.dart
│ │ │ ├── server_event.dart
│ │ │ └── server_state.dart
│ │ └── action_mapping/
│ │ ├── action_mapping_bloc.dart
│ │ ├── action_mapping_event.dart
│ │ └── action_mapping_state.dart
│ ├── screens/
│ │ ├── auth/
│ │ │ └── login_screen.dart
│ │ ├── servers/
│ │ │ ├── server_list_screen.dart
│ │ │ ├── server_detail_screen.dart
│ │ │ └── server_form_screen.dart
│ │ ├── action_mappings/
│ │ │ ├── action_mapping_list_screen.dart
│ │ │ └── action_mapping_form_screen.dart
│ │ ├── cameras/
│ │ │ ├── camera_list_screen.dart
│ │ │ └── camera_control_screen.dart
│ │ └── settings/
│ │ └── settings_screen.dart
│ └── widgets/
│ ├── common/
│ │ ├── loading_widget.dart
│ │ ├── error_widget.dart
│ │ └── empty_state_widget.dart
│ ├── server/
│ │ └── server_card.dart
│ └── action_mapping/
│ └── action_mapping_card.dart
├── injection.dart
└── main.dart
```
## API Integration
### Base Configuration
```dart
class ApiConfig {
static const String baseUrl = 'http://localhost:8000';
static const Duration timeout = Duration(seconds: 30);
static const Duration receiveTimeout = Duration(seconds: 30);
}
```
### Dio Setup with Interceptors
```dart
@singleton
class DioClient {
final Dio _dio;
DioClient() : _dio = Dio(BaseOptions(
baseUrl: ApiConfig.baseUrl,
connectTimeout: ApiConfig.timeout,
receiveTimeout: ApiConfig.receiveTimeout,
)) {
_dio.interceptors.addAll([
AuthInterceptor(),
LoggingInterceptor(),
]);
}
}
```
### Auth Interceptor (Token Management)
```dart
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final token = await secureStorage.read(key: 'access_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
// Token expired, try refresh
final refreshed = await refreshToken();
if (refreshed) {
// Retry original request
return handler.resolve(await _retry(err.requestOptions));
}
}
handler.next(err);
}
}
```
### Repository Pattern Example
```dart
@injectable
class ServerRepositoryImpl implements ServerRepository {
final ServerRemoteDataSource remoteDataSource;
final CacheManager cacheManager;
ServerRepositoryImpl({
required this.remoteDataSource,
required this.cacheManager,
});
@override
Future<Either<Failure, List<Server>>> getServers() async {
try {
// Try cache first
final cached = await cacheManager.getServers();
if (cached != null && !cached.isExpired) {
return Right(cached.data);
}
// Fetch from API
final servers = await remoteDataSource.getServers();
// Update cache
await cacheManager.saveServers(servers);
return Right(servers.map((m) => m.toEntity()).toList());
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} on NetworkException {
return Left(NetworkFailure());
}
}
}
```
## State Management Flow
### BLoC Pattern
```dart
// Event
abstract class ServerEvent {}
class LoadServers extends ServerEvent {}
class CreateServer extends ServerEvent {
final ServerCreateRequest request;
CreateServer(this.request);
}
// State
abstract class ServerState {}
class ServerInitial extends ServerState {}
class ServerLoading extends ServerState {}
class ServerLoaded extends ServerState {
final List<Server> servers;
ServerLoaded(this.servers);
}
class ServerError extends ServerState {
final String message;
ServerError(this.message);
}
// BLoC
class ServerBloc extends Bloc<ServerEvent, ServerState> {
final GetServers getServers;
final CreateServer createServer;
ServerBloc({
required this.getServers,
required this.createServer,
}) : super(ServerInitial()) {
on<LoadServers>(_onLoadServers);
on<CreateServer>(_onCreateServer);
}
Future<void> _onLoadServers(
LoadServers event,
Emitter<ServerState> emit,
) async {
emit(ServerLoading());
final result = await getServers();
result.fold(
(failure) => emit(ServerError(failure.message)),
(servers) => emit(ServerLoaded(servers)),
);
}
}
```
## Caching Strategy
### Cache Layers
1. **Memory Cache**: In-memory Map for frequently accessed data
2. **Disk Cache (Hive)**: Persistent storage for offline access
3. **Secure Storage**: Encrypted storage for tokens and credentials
### Cache Policy
```dart
class CachePolicy {
// Short-lived cache (5 minutes)
static const Duration shortCache = Duration(minutes: 5);
// Medium cache (30 minutes)
static const Duration mediumCache = Duration(minutes: 30);
// Long cache (24 hours)
static const Duration longCache = Duration(hours: 24);
}
// Server list: Short cache (changes frequently)
// Action mappings: Medium cache
// Configuration tree: Long cache
```
## Error Handling
### Error Types
```dart
abstract class Failure {
String get message;
}
class NetworkFailure extends Failure {
@override
String get message => 'No internet connection';
}
class ServerFailure extends Failure {
final String errorMessage;
ServerFailure(this.errorMessage);
@override
String get message => errorMessage;
}
class ValidationFailure extends Failure {
final Map<String, String> errors;
ValidationFailure(this.errors);
@override
String get message => 'Validation failed';
}
```
### UI Error Display
```dart
Widget buildError(Failure failure) {
if (failure is NetworkFailure) {
return ErrorWidget(
message: failure.message,
icon: Icons.wifi_off,
action: ElevatedButton(
onPressed: () => context.read<ServerBloc>().add(LoadServers()),
child: Text('Retry'),
),
);
}
// ... other failure types
}
```
## Testing Strategy
### Unit Tests
```dart
void main() {
late ServerRepositoryImpl repository;
late MockServerRemoteDataSource mockRemoteDataSource;
late MockCacheManager mockCacheManager;
setUp(() {
mockRemoteDataSource = MockServerRemoteDataSource();
mockCacheManager = MockCacheManager();
repository = ServerRepositoryImpl(
remoteDataSource: mockRemoteDataSource,
cacheManager: mockCacheManager,
);
});
group('getServers', () {
test('should return cached data when cache is valid', () async {
// Arrange
when(mockCacheManager.getServers())
.thenAnswer((_) async => CachedData([server1, server2]));
// Act
final result = await repository.getServers();
// Assert
expect(result, isA<Right<Failure, List<Server>>>());
verifyNever(mockRemoteDataSource.getServers());
});
});
}
```
### Widget Tests
```dart
void main() {
testWidgets('ServerListScreen displays servers', (tester) async {
// Arrange
final mockBloc = MockServerBloc();
when(mockBloc.state).thenReturn(ServerLoaded([server1, server2]));
// Act
await tester.pumpWidget(
MaterialApp(
home: BlocProvider<ServerBloc>.value(
value: mockBloc,
child: ServerListScreen(),
),
),
);
// Assert
expect(find.text('Server 1'), findsOneWidget);
expect(find.text('Server 2'), findsOneWidget);
});
}
```
## Build & Deployment
### Build Configurations
```yaml
# build.yaml
targets:
$default:
builders:
freezed:
enabled: true
json_serializable:
enabled: true
```
### Environment Configuration
```dart
// config.dart
abstract class Config {
static String get apiBaseUrl => _apiBaseUrl;
static String _apiBaseUrl = 'http://localhost:8000';
static void setDevelopment() {
_apiBaseUrl = 'http://localhost:8000';
}
static void setProduction() {
_apiBaseUrl = 'https://api.production.com';
}
}
```
### CI/CD Pipeline
```yaml
# .github/workflows/flutter.yml
name: Flutter CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: flutter analyze
- run: flutter test --coverage
- uses: codecov/codecov-action@v3
```
## Implementation Phases
### Phase 1: Foundation (Week 1-2)
- Project setup with dependencies
- Folder structure
- Dependency injection setup
- API client configuration
- Authentication flow
### Phase 2: Core Features (Week 3-4)
- Server management (list, create, update, delete)
- Action mapping management
- Camera list and details
### Phase 3: Advanced Features (Week 5-6)
- PTZ camera control
- Monitor management
- Cross-switching
- Configuration export
### Phase 4: Polish (Week 7-8)
- Offline support
- Error handling improvements
- Performance optimization
- UI/UX refinements
- Accessibility improvements
### Phase 5: Testing & Deployment (Week 9-10)
- Comprehensive testing
- User acceptance testing
- Bug fixes
- App store preparation
- Documentation
## Performance Optimization
### Image Loading
- Use `cached_network_image` with disk and memory cache
- Progressive JPEG for large images
- Thumbnail generation for lists
### List Performance
- Use `ListView.builder` for long lists
- Implement pagination for server/mapping lists
- Lazy loading of details
### Memory Management
- Dispose BLoCs and controllers properly
- Clear cache periodically
- Monitor memory usage in DevTools
### Network Optimization
- Batch API requests where possible
- Implement request debouncing for search
- Cancel pending requests on navigation
- Use HTTP/2 for multiplexing