Documentation ajoutée : - ARCHITECTURE.md : Clean Architecture par feature, BLoC pattern - OPTIMISATIONS_PERFORMANCE.md : Cache multi-niveaux, pagination, lazy loading - SECURITE_PRODUCTION.md : FlutterSecureStorage, JWT, HTTPS, ProGuard - CHANGELOG.md : Historique versions - CONTRIBUTING.md : Guide contribution - README.md : Mise à jour (build, env config) Widgets partagés : - file_upload_widget.dart : Upload fichiers (photos/PDFs) Cache : - lib/core/cache/ : Système cache L1/L2 (mémoire/disque) Dependencies : - pubspec.yaml : file_picker 8.1.2, injectable, dio Spec 001 : 27/27 tâches (100%) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
583 lines
13 KiB
Markdown
583 lines
13 KiB
Markdown
# Guide de Contribution - UnionFlow Mobile
|
|
|
|
Merci de votre intérêt pour contribuer à UnionFlow Mobile! Ce guide vous aidera à démarrer.
|
|
|
|
---
|
|
|
|
## Table des Matières
|
|
|
|
1. [Code of Conduct](#code-of-conduct)
|
|
2. [Comment Contribuer](#comment-contribuer)
|
|
3. [Setup Environnement](#setup-environnement)
|
|
4. [Standards de Code](#standards-de-code)
|
|
5. [Architecture](#architecture)
|
|
6. [Workflow Git](#workflow-git)
|
|
7. [Tests](#tests)
|
|
8. [Pull Requests](#pull-requests)
|
|
9. [Code Review](#code-review)
|
|
10. [Bonnes Pratiques](#bonnes-pratiques)
|
|
|
|
---
|
|
|
|
## Code of Conduct
|
|
|
|
En contribuant à ce projet, vous acceptez de respecter notre Code de Conduite:
|
|
|
|
- 🤝 Respecter tous les contributeurs
|
|
- 💬 Communication constructive et professionnelle
|
|
- 🎯 Focus sur le projet et ses objectifs
|
|
- 🚫 Zéro tolérance pour le harcèlement ou discrimination
|
|
|
|
---
|
|
|
|
## Comment Contribuer
|
|
|
|
### Types de Contributions
|
|
|
|
✅ **Bug Fixes** - Correction de bugs
|
|
✅ **Features** - Nouvelles fonctionnalités
|
|
✅ **Documentation** - Amélioration de la doc
|
|
✅ **Tests** - Ajout ou amélioration des tests
|
|
✅ **Performance** - Optimisations
|
|
✅ **Refactoring** - Amélioration du code existant
|
|
|
|
### Avant de Commencer
|
|
|
|
1. **Vérifier les issues existantes** - Peut-être que quelqu'un travaille déjà dessus
|
|
2. **Créer une issue** - Si le bug/feature n'existe pas encore
|
|
3. **Discuter** - Commenter l'issue pour proposer votre approche
|
|
4. **Assignation** - Se faire assigner l'issue avant de commencer
|
|
|
|
---
|
|
|
|
## Setup Environnement
|
|
|
|
### Prérequis
|
|
|
|
| Outil | Version Minimale |
|
|
|-------|------------------|
|
|
| Flutter | 3.5.3+ |
|
|
| Dart | 3.5.3+ |
|
|
| Git | Latest |
|
|
| Android Studio | Latest (pour Android) |
|
|
| Xcode | 14+ (pour iOS, macOS uniquement) |
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
# 1. Cloner le repo
|
|
git clone https://git.lions.dev/lionsdev/unionflow-mobile-apps.git
|
|
cd unionflow-mobile-apps
|
|
|
|
# 2. Installer les dépendances
|
|
flutter pub get
|
|
|
|
# 3. Générer le code (build_runner)
|
|
flutter pub run build_runner build --delete-conflicting-outputs
|
|
|
|
# 4. Vérifier que tout fonctionne
|
|
flutter doctor
|
|
flutter analyze
|
|
flutter test
|
|
```
|
|
|
|
### Configuration Backend Local
|
|
|
|
Pour développer en local, vous avez besoin du backend Quarkus:
|
|
|
|
```bash
|
|
cd ../unionflow-server-impl-quarkus
|
|
mvn clean quarkus:dev
|
|
```
|
|
|
|
Backend disponible sur: `http://localhost:8085`
|
|
|
|
---
|
|
|
|
## Standards de Code
|
|
|
|
### Linting
|
|
|
|
Nous utilisons **flutter_lints** avec configuration stricte.
|
|
|
|
```bash
|
|
# Analyser le code
|
|
flutter analyze
|
|
|
|
# Formater le code
|
|
flutter format lib/
|
|
```
|
|
|
|
### Conventions de Nommage
|
|
|
|
**Fichiers**: snake_case
|
|
```
|
|
✅ dashboard_bloc.dart
|
|
❌ DashboardBloc.dart
|
|
❌ dashboard-bloc.dart
|
|
```
|
|
|
|
**Classes**: PascalCase
|
|
```dart
|
|
✅ class DashboardBloc {}
|
|
❌ class dashboardBloc {}
|
|
❌ class Dashboard_Bloc {}
|
|
```
|
|
|
|
**Variables/Fonctions**: camelCase
|
|
```dart
|
|
✅ final userName = 'John';
|
|
✅ void getUserData() {}
|
|
❌ final user_name = 'John';
|
|
❌ void get_user_data() {}
|
|
```
|
|
|
|
**Constantes**: lowerCamelCase (pas SCREAMING_CASE en Dart)
|
|
```dart
|
|
✅ static const baseUrl = 'https://api.lions.dev';
|
|
❌ static const BASE_URL = 'https://api.lions.dev';
|
|
```
|
|
|
|
**Private members**: préfixe `_`
|
|
```dart
|
|
✅ String _privateVariable;
|
|
✅ void _privateMethod() {}
|
|
```
|
|
|
|
### Documentation
|
|
|
|
**Classes publiques**:
|
|
```dart
|
|
/// Service pour gérer l'authentification Keycloak.
|
|
///
|
|
/// Ce service gère le login, logout et refresh token
|
|
/// avec stockage sécurisé des credentials.
|
|
class KeycloakAuthService {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Méthodes publiques**:
|
|
```dart
|
|
/// Authentifie un utilisateur avec username et password.
|
|
///
|
|
/// Retourne [User] si succès, `null` si échec.
|
|
/// Stocke les tokens dans [FlutterSecureStorage].
|
|
Future<User?> login(String username, String password) async {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**TODO Comments**:
|
|
```dart
|
|
// TODO(username): Ajouter support biométrique
|
|
// FIXME(username): Bug lors du logout après timeout
|
|
// HACK(username): Workaround temporaire, à refactoriser
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
UnionFlow Mobile suit **Clean Architecture + BLoC**.
|
|
|
|
### Structure d'une Feature
|
|
|
|
```
|
|
features/
|
|
└── my_feature/
|
|
├── data/
|
|
│ ├── datasources/
|
|
│ │ └── my_feature_remote_datasource.dart
|
|
│ ├── models/
|
|
│ │ ├── my_model.dart
|
|
│ │ └── my_model.g.dart
|
|
│ └── repositories/
|
|
│ └── my_feature_repository_impl.dart
|
|
├── domain/
|
|
│ ├── entities/
|
|
│ │ └── my_entity.dart
|
|
│ ├── repositories/
|
|
│ │ └── my_feature_repository.dart
|
|
│ └── usecases/
|
|
│ └── get_my_data.dart
|
|
└── presentation/
|
|
├── bloc/
|
|
│ ├── my_feature_bloc.dart
|
|
│ ├── my_feature_event.dart
|
|
│ └── my_feature_state.dart
|
|
├── pages/
|
|
│ └── my_feature_page.dart
|
|
└── widgets/
|
|
└── my_feature_card.dart
|
|
```
|
|
|
|
### Règles de Dépendance
|
|
|
|
✅ **Presentation** peut dépendre de **Domain**
|
|
✅ **Data** peut dépendre de **Domain**
|
|
❌ **Domain** ne doit JAMAIS dépendre de **Data** ou **Presentation**
|
|
|
|
### Checklist d'une Nouvelle Feature
|
|
|
|
- [ ] Créer les **Entities** dans `domain/entities/`
|
|
- [ ] Créer le **Repository Interface** dans `domain/repositories/`
|
|
- [ ] Créer les **Use Cases** dans `domain/usecases/`
|
|
- [ ] Créer les **Models** dans `data/models/` avec `@JsonSerializable`
|
|
- [ ] Créer le **Data Source** dans `data/datasources/`
|
|
- [ ] Implémenter le **Repository** dans `data/repositories/`
|
|
- [ ] Créer **Events** dans `presentation/bloc/`
|
|
- [ ] Créer **States** dans `presentation/bloc/`
|
|
- [ ] Créer le **BLoC** dans `presentation/bloc/`
|
|
- [ ] Créer les **Pages** dans `presentation/pages/`
|
|
- [ ] Créer les **Widgets** dans `presentation/widgets/`
|
|
- [ ] Enregistrer dans **DI** (`core/di/injection_container.dart`)
|
|
- [ ] Ajouter **Tests**
|
|
- [ ] Mettre à jour **Documentation**
|
|
|
|
---
|
|
|
|
## Workflow Git
|
|
|
|
### Branches
|
|
|
|
**Branches principales**:
|
|
- `main` - Production (protégée)
|
|
- `develop` - Développement (protégée)
|
|
|
|
**Branches de feature**:
|
|
```bash
|
|
feature/<issue-number>-<short-description>
|
|
```
|
|
|
|
Exemples:
|
|
```
|
|
feature/123-add-biometric-login
|
|
feature/456-fix-dashboard-crash
|
|
feature/789-improve-cache-performance
|
|
```
|
|
|
|
### Workflow
|
|
|
|
```bash
|
|
# 1. Créer une branche depuis develop
|
|
git checkout develop
|
|
git pull origin develop
|
|
git checkout -b feature/123-add-biometric-login
|
|
|
|
# 2. Développer et commiter
|
|
git add .
|
|
git commit -m "feat: add biometric authentication"
|
|
|
|
# 3. Pousser la branche
|
|
git push origin feature/123-add-biometric-login
|
|
|
|
# 4. Créer une Pull Request vers develop
|
|
```
|
|
|
|
### Commits
|
|
|
|
Nous suivons **Conventional Commits**:
|
|
|
|
**Format**:
|
|
```
|
|
<type>(<scope>): <description>
|
|
|
|
[optional body]
|
|
|
|
[optional footer]
|
|
```
|
|
|
|
**Types**:
|
|
- `feat` - Nouvelle fonctionnalité
|
|
- `fix` - Correction de bug
|
|
- `docs` - Documentation
|
|
- `style` - Formatage (pas de changement de code)
|
|
- `refactor` - Refactoring (ni bug ni feature)
|
|
- `perf` - Optimisation de performance
|
|
- `test` - Ajout ou modification de tests
|
|
- `chore` - Maintenance (dépendances, config, etc.)
|
|
|
|
**Exemples**:
|
|
```bash
|
|
feat(auth): add biometric login support
|
|
fix(dashboard): resolve null pointer on stats refresh
|
|
docs(readme): update installation instructions
|
|
refactor(cache): simplify cache service implementation
|
|
test(members): add unit tests for member repository
|
|
perf(dashboard): optimize dashboard loading time
|
|
chore(deps): upgrade flutter to 3.5.4
|
|
```
|
|
|
|
**Scope**: module concerné (auth, dashboard, members, etc.)
|
|
|
|
**Description**:
|
|
- Impératif ("add" pas "added" ou "adds")
|
|
- Minuscule (pas de majuscule en début)
|
|
- Pas de point final
|
|
- Max 72 caractères
|
|
|
|
---
|
|
|
|
## Tests
|
|
|
|
### Tests Unitaires
|
|
|
|
**Domain Layer**:
|
|
```dart
|
|
// test/domain/usecases/get_dashboard_data_test.dart
|
|
void main() {
|
|
late GetDashboardData useCase;
|
|
late MockDashboardRepository mockRepository;
|
|
|
|
setUp(() {
|
|
mockRepository = MockDashboardRepository();
|
|
useCase = GetDashboardData(mockRepository);
|
|
});
|
|
|
|
test('should return DashboardEntity when repository call succeeds', () async {
|
|
// Arrange
|
|
when(mockRepository.getDashboardData())
|
|
.thenAnswer((_) async => Right(tDashboardEntity));
|
|
|
|
// Act
|
|
final result = await useCase();
|
|
|
|
// Assert
|
|
expect(result, Right(tDashboardEntity));
|
|
verify(mockRepository.getDashboardData());
|
|
verifyNoMoreInteractions(mockRepository);
|
|
});
|
|
}
|
|
```
|
|
|
|
**BLoC Tests**:
|
|
```dart
|
|
// test/presentation/bloc/dashboard_bloc_test.dart
|
|
void main() {
|
|
late DashboardBloc bloc;
|
|
late MockGetDashboardData mockGetDashboardData;
|
|
|
|
setUp(() {
|
|
mockGetDashboardData = MockGetDashboardData();
|
|
bloc = DashboardBloc(getDashboardData: mockGetDashboardData);
|
|
});
|
|
|
|
blocTest<DashboardBloc, DashboardState>(
|
|
'emits [Loading, Loaded] when LoadDashboardData succeeds',
|
|
build: () {
|
|
when(mockGetDashboardData()).thenAnswer(
|
|
(_) async => Right(tDashboard),
|
|
);
|
|
return bloc;
|
|
},
|
|
act: (bloc) => bloc.add(LoadDashboardData()),
|
|
expect: () => [
|
|
DashboardLoading(),
|
|
DashboardLoaded(tDashboard),
|
|
],
|
|
verify: (_) {
|
|
verify(mockGetDashboardData()).called(1);
|
|
},
|
|
);
|
|
}
|
|
```
|
|
|
|
### Lancer les Tests
|
|
|
|
```bash
|
|
# Tous les tests
|
|
flutter test
|
|
|
|
# Tests avec coverage
|
|
flutter test --coverage
|
|
|
|
# Visualiser coverage
|
|
genhtml coverage/lcov.info -o coverage/html
|
|
open coverage/html/index.html
|
|
```
|
|
|
|
### Coverage Minimum
|
|
|
|
**Target**: 80% de coverage minimum pour les nouvelles features
|
|
|
|
---
|
|
|
|
## Pull Requests
|
|
|
|
### Avant de Créer une PR
|
|
|
|
✅ **Code compilé**: `flutter build apk --debug`
|
|
✅ **Linting**: `flutter analyze` sans erreur
|
|
✅ **Tests**: `flutter test` 100% pass
|
|
✅ **Formaté**: `flutter format lib/`
|
|
✅ **Commits propres**: Utiliser Conventional Commits
|
|
|
|
### Template de PR
|
|
|
|
```markdown
|
|
## Description
|
|
|
|
Brève description du changement.
|
|
|
|
## Type de Changement
|
|
|
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
|
- [ ] New feature (non-breaking change which adds functionality)
|
|
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
|
- [ ] Documentation update
|
|
|
|
## Issue Liée
|
|
|
|
Closes #123
|
|
|
|
## Comment Tester
|
|
|
|
1. Lancer l'app
|
|
2. Naviguer vers Dashboard
|
|
3. Vérifier que les stats se chargent
|
|
|
|
## Checklist
|
|
|
|
- [ ] Mon code suit les conventions du projet
|
|
- [ ] J'ai commenté mon code aux endroits complexes
|
|
- [ ] J'ai mis à jour la documentation si nécessaire
|
|
- [ ] Mes changements ne génèrent pas de warnings
|
|
- [ ] J'ai ajouté des tests qui prouvent mon fix/feature
|
|
- [ ] Tous les tests passent localement
|
|
- [ ] J'ai vérifié que le code build en release
|
|
```
|
|
|
|
### Taille de PR
|
|
|
|
**Idéal**: 200-400 lignes
|
|
**Max acceptable**: 800 lignes
|
|
|
|
Si > 800 lignes, découper en plusieurs PRs.
|
|
|
|
---
|
|
|
|
## Code Review
|
|
|
|
### Pour le Reviewer
|
|
|
|
✅ **Architecture** - Respecte Clean Architecture + BLoC?
|
|
✅ **Tests** - Coverage suffisant?
|
|
✅ **Performance** - Pas de régressions?
|
|
✅ **Sécurité** - Pas de vulnérabilités?
|
|
✅ **Lisibilité** - Code compréhensible?
|
|
✅ **Documentation** - Suffisamment documenté?
|
|
|
|
### Pour l'Auteur
|
|
|
|
✅ **Répondre rapidement** aux commentaires
|
|
✅ **Argumenter** les choix techniques
|
|
✅ **Ne pas prendre personnellement** les critiques
|
|
✅ **Remercier** le reviewer
|
|
|
|
### Approuver une PR
|
|
|
|
Nécessite **2 approbations** minimum:
|
|
- 1 approbation d'un **Tech Lead**
|
|
- 1 approbation d'un **Peer**
|
|
|
|
---
|
|
|
|
## Bonnes Pratiques
|
|
|
|
### DRY (Don't Repeat Yourself)
|
|
|
|
❌ **Mauvais**:
|
|
```dart
|
|
// Dupliqué dans 5 fichiers
|
|
final baseUrl = 'https://api.lions.dev';
|
|
```
|
|
|
|
✅ **Bon**:
|
|
```dart
|
|
// core/config/environment.dart
|
|
class AppConfig {
|
|
static String get apiBaseUrl => _apiBaseUrl;
|
|
}
|
|
```
|
|
|
|
### KISS (Keep It Simple, Stupid)
|
|
|
|
❌ **Mauvais** (sur-ingénierie):
|
|
```dart
|
|
class AbstractFactoryProviderManagerBuilderFactory {
|
|
// 500 lignes de code complexe pour un simple getter
|
|
}
|
|
```
|
|
|
|
✅ **Bon**:
|
|
```dart
|
|
class CacheService {
|
|
T? get<T>(String key) => _prefs.get(key);
|
|
}
|
|
```
|
|
|
|
### YAGNI (You Aren't Gonna Need It)
|
|
|
|
❌ **Mauvais** (fonctionnalités inutilisées):
|
|
```dart
|
|
class User {
|
|
// 50 champs dont 40 jamais utilisés
|
|
}
|
|
```
|
|
|
|
✅ **Bon**:
|
|
```dart
|
|
class User {
|
|
// Seulement les champs nécessaires
|
|
final String id;
|
|
final String email;
|
|
final String name;
|
|
}
|
|
```
|
|
|
|
### Const Constructors
|
|
|
|
✅ **Toujours utiliser `const`** quand possible:
|
|
```dart
|
|
const Text('Hello')
|
|
const SizedBox(height: 16)
|
|
const EdgeInsets.all(8)
|
|
const Icon(Icons.home)
|
|
```
|
|
|
|
### Null Safety
|
|
|
|
✅ **Utiliser les opérateurs null-safety**:
|
|
```dart
|
|
final name = user?.name ?? 'Unknown';
|
|
final length = items?.length ?? 0;
|
|
final result = await api.call() ?? defaultValue;
|
|
```
|
|
|
|
---
|
|
|
|
## Questions?
|
|
|
|
Si vous avez des questions:
|
|
|
|
1. **Lire la documentation** - README, ARCHITECTURE, cette doc
|
|
2. **Chercher dans les issues** - Peut-être déjà répondu
|
|
3. **Demander sur Discord** - #unionflow-dev channel
|
|
4. **Créer une issue** - Si toujours bloqué
|
|
|
|
---
|
|
|
|
## Ressources
|
|
|
|
- [Flutter Docs](https://docs.flutter.dev/)
|
|
- [Dart Style Guide](https://dart.dev/guides/language/effective-dart/style)
|
|
- [BLoC Pattern](https://bloclibrary.dev/)
|
|
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
|
|
|
---
|
|
|
|
**Merci de contribuer à UnionFlow Mobile!** 🎉
|
|
|
|
*Document maintenu par l'équipe UnionFlow Development Team*
|