# 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>> 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 servers; ServerLoaded(this.servers); } class ServerError extends ServerState { final String message; ServerError(this.message); } // BLoC class ServerBloc extends Bloc { final GetServers getServers; final CreateServer createServer; ServerBloc({ required this.getServers, required this.createServer, }) : super(ServerInitial()) { on(_onLoadServers); on(_onCreateServer); } Future _onLoadServers( LoadServers event, Emitter 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 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().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>>()); 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.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