# CLAUDE.md - Plan B Logistics Flutter App This file provides guidance to Claude Code when working with this Flutter/Dart project. ## Project Overview Plan B Logistics Flutter is a complete refactor of the Ionic Angular delivery management app into Flutter/Dart, maintaining all functionality while applying Svrnty design system (colors, typography, and Material Design 3). **Key Features:** - OAuth2/OIDC authentication with Keycloak - CQRS pattern for API integration with Result error handling - Delivery route and delivery management - Photo upload for delivery proof - i18n support (French/English) - Native features: Camera, Phone calls, Maps ## Essential Commands ### Setup & Dependencies ```bash flutter pub get flutter pub upgrade flutter pub run build_runner build --delete-conflicting-outputs ``` ### Development ```bash flutter run -d chrome # Web flutter run -d macos # macOS flutter run -d ios # iOS simulator flutter run -d android # Android emulator ``` ### Testing & Analysis ```bash flutter test flutter analyze flutter test --coverage ``` ### Build ```bash flutter build web flutter build ios flutter build android ``` ## Code Architecture ### Core Structure ``` lib/ ├── api/ # CQRS API client and types │ ├── types.dart # Result, Serializable, ApiError │ ├── client.dart # CqrsApiClient implementation │ └── openapi_config.dart ├── models/ # Data models (strict typing) │ ├── delivery.dart │ ├── delivery_route.dart │ ├── user_profile.dart │ └── ... ├── services/ # Business logic │ └── auth_service.dart ├── providers/ # Riverpod state management │ └── providers.dart ├── pages/ # Screen widgets │ ├── login_page.dart │ ├── routes_page.dart │ ├── deliveries_page.dart │ └── settings_page.dart ├── components/ # Reusable UI components ├── l10n/ # i18n translations (*.arb files) ├── utils/ # Utility functions ├── theme.dart # Svrnty theme configuration └── main.dart # App entry point ``` ### Design System (Svrnty) **Primary Colors:** - Primary (Crimson): #C91F37 (light) / #FF5A6D (dark) - Secondary (Slate Blue): #2D3843 (light) / #A5B6C8 (dark) - Tertiary (Green): #16803D (light) / #5EE890 (dark) - Error: #D32F2F (light) / #FF8A80 (dark) **Typography:** - Primary Font: Montserrat (all weights 300-700) - Monospace Font: IBMPlexMono - Material Design 3 text styles with explicit color assignments **Theme System:** - **2 Theme Variants:** Light and Dark (forest green background) - **WCAG AAA Compliant:** All text meets 7:1 contrast minimum - **Dark Theme Background:** Forest Green (#0C1410) - unique branding - **No Hardcoded Colors:** All components use ColorScheme properties - **Theme Files:** - `lib/theme.dart` - Material 3 theme with 2 ColorScheme variants - `lib/theme/color_system.dart` - Svrnty color constants - `lib/theme/status_colors.dart` - Status color utilities - `lib/theme/component_themes.dart` - Component-specific themes - `lib/theme/README.md` - Complete theme documentation ## Core Patterns & Standards ### 1. Strict Typing (MANDATORY) **NO `dynamic`, NO untyped `var`** ```dart // FORBIDDEN: var data = fetchData(); dynamic result = api.call(); // REQUIRED: DeliveryRoute data = fetchData(); Result result = api.call(); ``` ### 2. Serializable Interface All models must implement `Serializable`: ```dart class Delivery implements Serializable { final int id; final String name; const Delivery({required this.id, required this.name}); factory Delivery.fromJson(Map json) { return Delivery( id: json['id'] as int, name: json['name'] as String, ); } @override Map toJson() => { 'id': id, 'name': name, }; } ``` ### 3. Error Handling with Result **NEVER use try-catch for API calls. Use Result pattern:** ```dart final result = await apiClient.executeQuery( endpoint: 'deliveryRoutes', query: GetRoutesQuery(), fromJson: DeliveryRoute.fromJson, ); result.when( success: (route) => showRoute(route), error: (error) { switch (error.type) { case ApiErrorType.network: showSnackbar('No connection'); case ApiErrorType.timeout: showSnackbar('Request timeout'); case ApiErrorType.validation: showValidationErrors(error.details); case ApiErrorType.http when error.statusCode == 401: navigateToLogin(); default: showSnackbar('Error: ${error.message}'); } }, ); ``` ### 4. CQRS API Integration **Query (Read Operations):** ```dart final result = await client.executeQuery( endpoint: 'simpleDeliveriesQueryItems', query: GetDeliveriesQuery(routeFragmentId: 123), fromJson: Delivery.fromJson, ); ``` **Command (Write Operations):** ```dart final result = await client.executeCommand( endpoint: 'completeDelivery', command: CompleteDeliveryCommand(deliveryId: 123), ); ``` ### 5. Riverpod State Management **Providers Pattern:** ```dart final authServiceProvider = Provider((ref) { return AuthService(); }); final userProfileProvider = FutureProvider((ref) async { final authService = ref.watch(authServiceProvider); final token = await authService.getToken(); return token != null ? authService.decodeToken(token) : null; }); // Usage in widget: final userProfile = ref.watch(userProfileProvider); userProfile.when( data: (profile) => Text(profile?.fullName ?? ''), loading: () => const CircularProgressIndicator(), error: (error, stackTrace) => Text('Error: $error'), ); ``` ### 6. Authentication Flow 1. **Login**: `AuthService.login()` triggers OAuth2/OIDC flow with Keycloak 2. **Token Storage**: Secure storage with `flutter_secure_storage` 3. **Token Validation**: Check expiration with `JwtDecoder.isExpired()` 4. **Auto Refresh**: Implement token refresh on 401 responses 5. **Logout**: Clear tokens from secure storage **Keycloak Configuration:** - Realm: planb-internal - Client ID: delivery-mobile-app - Discovery URL: https://auth.goutezplanb.com/realms/planb-internal/.well-known/openid-configuration - Scopes: openid, profile, offline_access ### 7. No Emojis Rule **MANDATORY: NO emojis in code, comments, or commit messages** ```dart // FORBIDDEN: // Bug fix for delivery issues void completeDelivery(int id) { ... } // Done // REQUIRED: // Bug fix for delivery completion logic void completeDelivery(int id) { ... } ``` ### 8. Theme System (MANDATORY) **Standard Color Access Pattern (use everywhere):** ```dart final colorScheme = Theme.of(context).colorScheme; // Primary UI color: colorScheme.primary // Primary brand color color: colorScheme.onPrimary // Text on primary // Text colors color: colorScheme.onSurface // Primary text color: colorScheme.onSurfaceVariant // Secondary text // Backgrounds color: colorScheme.surface // Page background color: colorScheme.surfaceContainer // Card background // Shadows color: colorScheme.scrim.withValues(alpha: 0.2) ``` **FORBIDDEN Patterns:** ```dart // NEVER use these in component files: color: Colors.white // FORBIDDEN color: Colors.black // FORBIDDEN color: Color(0xFFXXXXXX) // FORBIDDEN (except in theme files) color: SvrntyColors.crimsonRed // FORBIDDEN - use colorScheme.primary ``` **Dark Theme:** The app uses a **forest green dark theme** (#0C1410) for unique branding. All text colors automatically adapt with WCAG AAA compliance (7:1 minimum contrast). **Status Colors:** ```dart import '../theme/status_colors.dart'; // Theme-aware status colors (preferred) StatusColorScheme.getStatusColorFromTheme('completed', colorScheme) // Hardcoded status colors (fallback) StatusColorScheme.getStatusColor('completed') ``` **Modifying Theme:** To change brand colors, edit `/lib/theme.dart`: - `lightScheme()` - Light theme ColorScheme - `darkScheme()` - Dark theme ColorScheme **Documentation:** See `/lib/theme/README.md` for complete theme system documentation. ## API Integration ### Base URLs ```dart const String queryBaseUrl = 'https://api-route.goutezplanb.com/api/query'; const String commandBaseUrl = 'https://api-route.goutezplanb.com/api/command'; ``` ### Key Endpoints - Query: `/api/query/simpleDeliveriesQueryItems` - Query: `/api/query/simpleDeliveryRouteQueryItems` - Command: `/api/command/completeDelivery` - Command: `/api/command/markDeliveryAsUncompleted` - Upload: `/api/delivery/uploadDeliveryPicture` ### Authorization All requests to API base URL must include Bearer token: ```dart final authClient = CqrsApiClient( config: ApiClientConfig( baseUrl: 'https://api-route.goutezplanb.com', defaultHeaders: {'Authorization': 'Bearer $token'}, ), ); ``` ## Internationalization (i18n) ### File Structure ``` lib/l10n/ ├── app_en.arb # English translations └── app_fr.arb # French translations ``` ### ARB Format ```json { "appTitle": "Plan B Logistics", "loginButton": "Login with Keycloak", "deliveryStatus": "Delivery #{id} is {status}", "@deliveryStatus": { "placeholders": { "id": {"type": "int"}, "status": {"type": "String"} } } } ``` ### Usage in Code ```dart AppLocalizations.of(context)!.appTitle AppLocalizations.of(context)!.deliveryStatus(id: 123, status: 'completed') ``` ## Native Features ### Camera Integration - Package: `image_picker` - Use: Photo capture for delivery proof - Platforms: iOS, Android, Web ```dart final picker = ImagePicker(); final pickedFile = await picker.pickImage(source: ImageSource.camera); if (pickedFile != null) { // Upload to server } ``` ### Phone Calls - Package: `url_launcher` - Use: Call customer from delivery details ```dart final Uri phoneUri = Uri(scheme: 'tel', path: phoneNumber); if (await canLaunchUrl(phoneUri)) { await launchUrl(phoneUri); } ``` ### Maps Integration - Package: `url_launcher` - Use: Open maps app to show delivery address ```dart final Uri mapUri = Uri( scheme: 'https', host: 'maps.google.com', queryParameters: {'q': '${address.latitude},${address.longitude}'}, ); if (await canLaunchUrl(mapUri)) { await launchUrl(mapUri); } ``` ## File Naming Conventions - **Files**: snake_case (e.g., `delivery_route.dart`) - **Classes**: PascalCase (e.g., `DeliveryRoute`) - **Variables/Functions**: camelCase (e.g., `deliveryId`, `completeDelivery()`) - **Constants**: camelCase or UPPER_SNAKE_CASE (e.g., `kPrimaryColor` or `MAX_RETRIES`) - **Private members**: Prefix with underscore (e.g., `_secureStorage`) ## Git Conventions - **Author**: Svrnty - **Co-Author**: Jean-Philippe Brule - **Commits**: Clear, concise messages describing the "why" - **NO emojis in commits** Example: ``` Implement OAuth2/OIDC authentication with Keycloak Adds AuthService with flutter_appauth integration, JWT token management with secure storage, and automatic token refresh. Co-Authored-By: Claude ``` ## Common Development Tasks ### Adding a New Page 1. Create widget file in `lib/pages/[name]_page.dart` 2. Extend `ConsumerWidget` for Riverpod access 3. Use strict typing for all parameters and variables 4. Apply Svrnty colors from theme 5. Handle loading/error states with `.when()` ### Adding a New Data Model 1. Create in `lib/models/[name].dart` 2. Implement `Serializable` interface 3. Add `fromJson` factory constructor 4. Implement `toJson()` method 5. Use explicit types (no `dynamic`) ### Implementing API Call 1. Create Query/Command class implementing `Serializable` 2. Use `CqrsApiClient.executeQuery()` or `.executeCommand()` 3. Handle Result with `.when()` pattern 4. Never use try-catch for API calls 5. Provide proper error messages to user ### Adding i18n Support 1. Add key to `app_en.arb` and `app_fr.arb` 2. Use `AppLocalizations.of(context)!.keyName` in widgets 3. For parameterized strings, define placeholders in ARB 4. Test both English and French text ## Testing - Unit tests: `test/` directory - Widget tests: `test/` directory with widget_test suffix - Use Riverpod's testing utilities for provider testing - Mock API client for service tests - Maintain >80% code coverage for business logic ## Deployment Checklist - [ ] All strict typing rules followed - [ ] No `dynamic` or untyped `var` - [ ] All API calls use Result pattern - [ ] i18n translations complete for both languages - [ ] Theme colors correctly applied - [ ] No emojis in code or commits - [ ] Tests passing (flutter test) - [ ] Static analysis clean (flutter analyze) - [ ] No secrets in code (tokens, keys, credentials) - [ ] APK/IPA builds successful ## Key Dependencies - `flutter_riverpod`: State management - `flutter_appauth`: OAuth2/OIDC - `flutter_secure_storage`: Token storage - `jwt_decoder`: JWT token parsing - `http`: HTTP client - `image_picker`: Camera/photo access - `url_launcher`: Phone calls and maps - `animate_do`: Animations (from Svrnty) - `lottie`: Loading animations - `iconsax`: Icon set - `intl`: Internationalization ## Support & Documentation - **Theme**: See `lib/theme.dart` for complete Svrnty design system - **API Types**: See `lib/api/types.dart` for Result and error handling - **Models**: See `lib/models/` for data structure examples - **Providers**: See `lib/providers/providers.dart` for state management setup - **Auth**: See `lib/services/auth_service.dart` for OAuth2/OIDC flow