Implements complete refactor of Ionic Angular logistics app to Flutter/Dart with: - Svrnty dark mode console theme (Material Design 3) - Responsive layouts (mobile, tablet, desktop) following FRONTEND standards - CQRS API integration with Result<T> error handling - OAuth2/OIDC authentication support (mocked for initial testing) - Delivery route and delivery management features - Multi-language support (EN/FR) with i18n - Native integrations (camera, phone calls, maps) - Strict typing throughout codebase - Mock data for UI testing without backend Follows all FRONTEND style guides, design patterns, and conventions. App is running in dark mode and fully responsive across all device sizes. Co-Authored-By: Claude <noreply@anthropic.com>
11 KiB
11 KiB
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
flutter pub get
flutter pub upgrade
flutter pub run build_runner build --delete-conflicting-outputs
Development
flutter run -d chrome # Web
flutter run -d macos # macOS
flutter run -d ios # iOS simulator
flutter run -d android # Android emulator
Testing & Analysis
flutter test
flutter analyze
flutter test --coverage
Build
flutter build web
flutter build ios
flutter build android
Code Architecture
Core Structure
lib/
├── api/ # CQRS API client and types
│ ├── types.dart # Result<T>, 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): #C44D58
- Secondary (Slate Blue): #475C6C
- Error: #BA1A1A
Typography:
- Primary Font: Montserrat (all weights 300-700)
- Monospace Font: IBMPlexMono
- Material Design 3 text styles
Theme Files:
lib/theme.dart- Complete Material 3 theme configuration- Light and dark themes with high-contrast variants
- All colors defined in ColorScheme
Core Patterns & Standards
1. Strict Typing (MANDATORY)
NO dynamic, NO untyped var
// FORBIDDEN:
var data = fetchData();
dynamic result = api.call();
// REQUIRED:
DeliveryRoute data = fetchData();
Result<DeliveryRoute> result = api.call();
2. Serializable Interface
All models must implement Serializable:
class Delivery implements Serializable {
final int id;
final String name;
const Delivery({required this.id, required this.name});
factory Delivery.fromJson(Map<String, dynamic> json) {
return Delivery(
id: json['id'] as int,
name: json['name'] as String,
);
}
@override
Map<String, Object?> toJson() => {
'id': id,
'name': name,
};
}
3. Error Handling with Result
NEVER use try-catch for API calls. Use Result pattern:
final result = await apiClient.executeQuery<DeliveryRoute>(
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):
final result = await client.executeQuery<Delivery>(
endpoint: 'simpleDeliveriesQueryItems',
query: GetDeliveriesQuery(routeFragmentId: 123),
fromJson: Delivery.fromJson,
);
Command (Write Operations):
final result = await client.executeCommand(
endpoint: 'completeDelivery',
command: CompleteDeliveryCommand(deliveryId: 123),
);
5. Riverpod State Management
Providers Pattern:
final authServiceProvider = Provider<AuthService>((ref) {
return AuthService();
});
final userProfileProvider = FutureProvider<UserProfile?>((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
- Login:
AuthService.login()triggers OAuth2/OIDC flow with Keycloak - Token Storage: Secure storage with
flutter_secure_storage - Token Validation: Check expiration with
JwtDecoder.isExpired() - Auto Refresh: Implement token refresh on 401 responses
- 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
// FORBIDDEN:
// Bug fix for delivery issues
void completeDelivery(int id) { ... } // Done
// REQUIRED:
// Bug fix for delivery completion logic
void completeDelivery(int id) { ... }
API Integration
Base URLs
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:
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
{
"appTitle": "Plan B Logistics",
"loginButton": "Login with Keycloak",
"deliveryStatus": "Delivery #{id} is {status}",
"@deliveryStatus": {
"placeholders": {
"id": {"type": "int"},
"status": {"type": "String"}
}
}
}
Usage in Code
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
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
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
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.,
kPrimaryColororMAX_RETRIES) - Private members: Prefix with underscore (e.g.,
_secureStorage)
Git Conventions
- Author: Svrnty
- Co-Author: Jean-Philippe Brule jp@svrnty.io
- 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 <noreply@anthropic.com>
Common Development Tasks
Adding a New Page
- Create widget file in
lib/pages/[name]_page.dart - Extend
ConsumerWidgetfor Riverpod access - Use strict typing for all parameters and variables
- Apply Svrnty colors from theme
- Handle loading/error states with
.when()
Adding a New Data Model
- Create in
lib/models/[name].dart - Implement
Serializableinterface - Add
fromJsonfactory constructor - Implement
toJson()method - Use explicit types (no
dynamic)
Implementing API Call
- Create Query/Command class implementing
Serializable - Use
CqrsApiClient.executeQuery()or.executeCommand() - Handle Result with
.when()pattern - Never use try-catch for API calls
- Provide proper error messages to user
Adding i18n Support
- Add key to
app_en.arbandapp_fr.arb - Use
AppLocalizations.of(context)!.keyNamein widgets - For parameterized strings, define placeholders in ARB
- 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
dynamicor untypedvar - 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 managementflutter_appauth: OAuth2/OIDCflutter_secure_storage: Token storagejwt_decoder: JWT token parsinghttp: HTTP clientimage_picker: Camera/photo accessurl_launcher: Phone calls and mapsanimate_do: Animations (from Svrnty)lottie: Loading animationsiconsax: Icon setintl: Internationalization
Support & Documentation
- Theme: See
lib/theme.dartfor complete Svrnty design system - API Types: See
lib/api/types.dartfor Result and error handling - Models: See
lib/models/for data structure examples - Providers: See
lib/providers/providers.dartfor state management setup - Auth: See
lib/services/auth_service.dartfor OAuth2/OIDC flow