etape4
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Reprise du projet — où nous en sommes
|
||||
|
||||
> Dernière mise à jour : projet Flutter créé dans `swiftyCompanion/swifty_companion/`.
|
||||
> Dernière mise à jour : étape 4 codée (profil complet).
|
||||
|
||||
## En bref
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|---|---|
|
||||
| **Fait** | Structure dépôt perso, sujet FR, paquet rendu, **projet Flutter** (`swifty_companion/`) |
|
||||
| **Fait** | OAuth + token + `/v2/me` (login connecté) |
|
||||
| **Prochaine action** | **Étape 4** — [vue profil complète](etape-04-vue-profil.md) |
|
||||
| **Fait** | Vue 1 recherche + vue 2 profil (skills, projets, ≥ 4 champs) |
|
||||
| **Prochaine action** | **Étape 5** — retour / layout + tests manuels sur device |
|
||||
|
||||
## Arborescence actuelle
|
||||
|
||||
@@ -27,7 +28,6 @@ swifty_companion/ ← dépôt Git de TRAVAIL (perso)
|
||||
│ └── 00-reprise-et-etat.md ← ce fichier
|
||||
└── swiftyCompanion/ ← futur dépôt ÉCOLE (racine Vogosphere)
|
||||
├── README.md
|
||||
├── .gitignore
|
||||
├── .env.example
|
||||
└── swifty_companion/ ← projet Flutter (lib/, android/, pubspec.yaml)
|
||||
```
|
||||
@@ -35,9 +35,10 @@ swifty_companion/ ← dépôt Git de TRAVAIL (perso)
|
||||
## Décisions déjà prises
|
||||
|
||||
1. **Deux dépôts Git** : tout `docs/` et la traduction restent dans le repo perso ; l’Intra ne reçoit que le contenu de `swiftyCompanion/`.
|
||||
2. **Stack prévue** : Flutter (cohérent avec `piscine_mobile/`), modifiable en soutenance si justifié.
|
||||
2. **Stack** : Flutter, `flutter_dotenv`, `flutter_appauth`, `http`.
|
||||
3. **Secrets** : `.env` local uniquement, jamais commité (modèle dans `swiftyCompanion/.env.example`).
|
||||
4. **Sujet PDF** : pas de maquettes d’écrans — l’UI suit la liste des critères obligatoires ([sujet FR](../sujets/swifty-companion.md)).
|
||||
4. **Redirect OAuth mobile** : `com.example.swiftycompanion://callback` (pas `localhost` sur téléphone physique).
|
||||
5. **Sujet PDF** : pas de maquettes d’écrans — l’UI suit la liste des critères obligatoires ([sujet FR](../sujets/swifty-companion.md)).
|
||||
|
||||
## Avancement par étape
|
||||
|
||||
@@ -45,25 +46,37 @@ swifty_companion/ ← dépôt Git de TRAVAIL (perso)
|
||||
|-------|---------|--------|
|
||||
| **0** | Structure, traduction sujet, séparation travail / école, README rendu | ✅ **Terminé** |
|
||||
| **1** | Projet Flutter `swiftyCompanion/swifty_companion/` | ✅ **Terminé** |
|
||||
| **2** | OAuth2 (`IntraAuthService`, `/v2/me`) | ✅ **Validé** (`fgras-ca`) |
|
||||
| **3** | Vue 1 : `SearchScreen` + `IntraUserService` + navigation profil | ✅ **Codé** — à tester sur téléphone |
|
||||
| **4** | Vue 2 : profil complet (≥ 4 champs, skills, projets) | ⏳ **Prochaine** |
|
||||
| **5** | Retour vue 1 + layout adaptatif | ⏳ |
|
||||
| **2** | OAuth2 (`IntraAuthService`, `/v2/me`) | ✅ **Validé** |
|
||||
| **3** | Vue 1 : `SearchScreen` + `IntraUserService` + navigation profil | ✅ **Codé** — tester sur téléphone |
|
||||
| **4** | Vue 2 : `IntraProfileService` + profil (skills, projets, champs) | ✅ **Codé** — tester sur téléphone |
|
||||
| **5** | Retour vue 1 + layout adaptatif (polish) | ⏳ **Prochaine** |
|
||||
| **6** | Passage checklist [swiftyCompanion/README.md](../../swiftyCompanion/README.md) | ⏳ |
|
||||
| **7** | Bonus : refresh token à expiration | ⏳ |
|
||||
|
||||
## Code applicatif (`swifty_companion/lib/`)
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---------|------|
|
||||
| `config/intra_config.dart` | `.env`, URLs OAuth / API |
|
||||
| `services/intra_auth_service.dart` | OAuth + token unique + `GET /v2/me` |
|
||||
| `services/intra_user_service.dart` | `GET /v2/users?filter[login]=` |
|
||||
| `services/intra_profile_service.dart` | Profil : user, cursus, experiences, projects_users |
|
||||
| `services/intra_api_exception.dart` | Erreurs métier |
|
||||
| `screens/search_screen.dart` | **Vue 1** |
|
||||
| `screens/profile_screen.dart` | **Vue 2** |
|
||||
| `main.dart` | `home: SearchScreen()` |
|
||||
|
||||
## Reprendre le travail (checklist)
|
||||
|
||||
1. Ouvrir le dépôt : `~/projets/42School/swifty_companion`
|
||||
2. Relire le [sujet traduit](../sujets/swifty-companion.md) (section obligatoire).
|
||||
3. Vérifier Flutter : `flutter doctor` et `flutter devices` (voir [piscine — Ubuntu natif](../../../piscine_mobile/docs/pedagogie/flutter/09-ubuntu-natif-dev-android.md) si besoin).
|
||||
4. Lancer l’app :
|
||||
3. Vérifier Flutter : `flutter doctor` et `flutter devices`.
|
||||
4. Lancer l’app (après modification `.env`, relancer `flutter run`) :
|
||||
```bash
|
||||
cd swiftyCompanion/swifty_companion
|
||||
flutter run -d <device>
|
||||
```
|
||||
5. **Étape 2** — suivre [etape-02-oauth-intra.md](etape-02-oauth-intra.md) (app OAuth Intra, `.env`, service token unique).
|
||||
6. Dans le chat : demander l’**implémentation pas à pas** du code OAuth quand l’app OAuth Intra est créée.
|
||||
5. Tester le parcours : OAuth → recherche → profil → retour → erreurs réseau.
|
||||
|
||||
## Fichiers utiles selon le contexte
|
||||
|
||||
@@ -71,23 +84,22 @@ swifty_companion/ ← dépôt Git de TRAVAIL (perso)
|
||||
|----------|---------|
|
||||
| Comprendre le sujet | [docs/sujets/swifty-companion.md](../sujets/swifty-companion.md) |
|
||||
| Plan complet des étapes | [docs/pedagogie/README.md](README.md) |
|
||||
| Vue profil (étape 4) | [etape-04-vue-profil.md](etape-04-vue-profil.md) |
|
||||
| Workflow Git travail / école | [README racine](../../README.md) |
|
||||
| Soutenance / peer-eval | [swiftyCompanion/README.md](../../swiftyCompanion/README.md) |
|
||||
| Environnement Flutter | [piscine_mobile](../../../piscine_mobile/README.md) |
|
||||
|
||||
## Guides détaillés (à créer au fil du code)
|
||||
## Guides détaillés
|
||||
|
||||
| Fichier prévu | Quand |
|
||||
|---------------|--------|
|
||||
| `etape-01-flutter-init.md` | Après validation étape 1 |
|
||||
| `etape-02-oauth.md` | Début OAuth Intra |
|
||||
| `etape-03-vue-recherche.md` | Vue 1 |
|
||||
| `etape-04-vue-profil.md` | Vue 2 + API |
|
||||
| `etape-05-navigation-layout.md` | Navigation + responsive |
|
||||
| `etape-06-bonus-token.md` | Bonus |
|
||||
| Fichier | Statut |
|
||||
|---------|--------|
|
||||
| [etape-02-oauth-intra.md](etape-02-oauth-intra.md) | ✅ |
|
||||
| [etape-03-vue-recherche.md](etape-03-vue-recherche.md) | ✅ |
|
||||
| [etape-04-vue-profil.md](etape-04-vue-profil.md) | ✅ |
|
||||
| `etape-05-navigation-layout.md` | 🔜 |
|
||||
| `etape-06-bonus-token.md` | 🔜 |
|
||||
|
||||
## Notes pour la session suivante
|
||||
|
||||
- Code : projet Flutter par défaut (`lib/main.dart` compteur) — OAuth et vues à implémenter.
|
||||
- Valider étapes 3–4 sur **device réel** (recherche, profil, mode avion sur vue 2).
|
||||
- Étape 5 : harmoniser marges / contraintes sur les deux écrans si besoin en soutenance.
|
||||
- Le dépôt perso peut versionner le code dans `swiftyCompanion/` ; le remote **école** pointe uniquement sur ce sous-dossier (voir README racine).
|
||||
- Aucun `git init` obligatoire dans `swiftyCompanion/` tant que tu n’es pas prêt à lier Vogosphere.
|
||||
|
||||
@@ -4,7 +4,7 @@ Parcours pour construire le projet **étape par étape**.
|
||||
|
||||
## Reprise rapide
|
||||
|
||||
→ **[00-reprise-et-etat.md](00-reprise-et-etat.md)** — état actuel, arborescence, prochaine action (**étape 1 : `flutter create`**).
|
||||
→ **[00-reprise-et-etat.md](00-reprise-et-etat.md)** — état actuel, arborescence, prochaine action (**étape 5 : layout / tests**).
|
||||
|
||||
## Prérequis
|
||||
|
||||
@@ -17,13 +17,12 @@ Base via la [piscine mobile](../../../piscine_mobile/README.md) sur le même dis
|
||||
|
||||
| Étape | Objectif | Statut |
|
||||
|-------|----------|--------|
|
||||
| **0** | Dépôt travail + `docs/`, sujet FR, paquet `swiftyCompanion/` (README / `.gitignore` / `.env.example`) | ✅ **Fait** — détail dans [00-reprise-et-etat.md](00-reprise-et-etat.md) |
|
||||
| **0** | Dépôt travail + `docs/`, sujet FR, paquet `swiftyCompanion/` | ✅ **Fait** — [00-reprise-et-etat.md](00-reprise-et-etat.md) |
|
||||
| **1** | Projet `swiftyCompanion/swifty_companion/` + `flutter run` | ✅ **Fait** |
|
||||
| **2** | OAuth2 Intra : token + `/v2/me` | ✅ Fait |
|
||||
| **3** | **Vue 1** : recherche par login + erreurs | ✅ Codé → tester · [etape-03-vue-recherche.md](etape-03-vue-recherche.md) |
|
||||
| **4** | Vue 2 : profil complet | ⏳ **Prochaine** → [etape-04-vue-profil.md](etape-04-vue-profil.md) |
|
||||
| **4** | **Vue 2** : profil (≥ 4 champs + image), skills, projets (validés + failed) | ⏳ |
|
||||
| **5** | Navigation retour + layout responsive | ⏳ |
|
||||
| **2** | OAuth2 Intra : token + `/v2/me` | ✅ **Fait** — [etape-02-oauth-intra.md](etape-02-oauth-intra.md) |
|
||||
| **3** | **Vue 1** : recherche par login + erreurs | ✅ **Codé** — [etape-03-vue-recherche.md](etape-03-vue-recherche.md) |
|
||||
| **4** | **Vue 2** : profil (≥ 4 champs, skills, projets failed) | ✅ **Codé** — [etape-04-vue-profil.md](etape-04-vue-profil.md) |
|
||||
| **5** | Navigation retour + layout responsive (polish) | ⏳ **Prochaine** |
|
||||
| **6** | Tests manuels + checklist peer-eval | ⏳ |
|
||||
| **7** | **Bonus** : refresh token à l’expiration | ⏳ |
|
||||
|
||||
@@ -40,9 +39,8 @@ README correcteurs (dépôt école) : [swiftyCompanion/README.md](../../swiftyCo
|
||||
| [00-reprise-et-etat.md](00-reprise-et-etat.md) | ✅ À jour |
|
||||
| [etape-02-oauth-intra.md](etape-02-oauth-intra.md) | ✅ Guide étape 2 |
|
||||
| [etape-03-vue-recherche.md](etape-03-vue-recherche.md) | ✅ Guide étape 3 |
|
||||
| [etape-04-vue-profil.md](etape-04-vue-profil.md) | 🔜 après étape 3 |
|
||||
| `etape-04-vue-profil.md` | 🔜 |
|
||||
| [etape-04-vue-profil.md](etape-04-vue-profil.md) | ✅ Guide étape 4 |
|
||||
| `etape-05-navigation-layout.md` | 🔜 |
|
||||
| `etape-06-bonus-token.md` | 🔜 |
|
||||
|
||||
Demande dans le chat l’étape à ouvrir : le guide correspondant sera rédigé au moment où tu la attaques.
|
||||
Demande dans le chat l’étape à ouvrir : le guide correspondant sera rédigé au moment où tu l’attaques.
|
||||
|
||||
@@ -85,7 +85,7 @@ Endpoints 42 (référence) :
|
||||
| Token | `POST https://api.intra.42.fr/oauth/token` |
|
||||
| API | `GET https://api.intra.42.fr/v2/...` avec header `Authorization: Bearer <token>` |
|
||||
|
||||
Paramètres authorize usuels : `client_id`, `redirect_uri`, `response_type=code`, `scope=public`.
|
||||
Paramètres authorize usuels : `client_id`, `redirect_uri`, `response_type=code`, `scope=public projects` (voir [intra-oauth-formulaire.md](intra-oauth-formulaire.md)).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,21 +1,113 @@
|
||||
# Étape 4 — Vue 2 : profil complet
|
||||
|
||||
> **Prérequis :** [étape 3 — recherche](etape-03-vue-recherche.md) (navigation vers vue 2 avec login valide).
|
||||
> **Prérequis :** [étape 3 — recherche](etape-03-vue-recherche.md) (navigation vers vue 2 avec un login valide).
|
||||
|
||||
## Objectif sujet (partie obligatoire)
|
||||
## Objectif sujet
|
||||
|
||||
| Exigence | Détail |
|
||||
|----------|--------|
|
||||
| ≥ 4 infos | login, email, mobile, level, location, wallet, evaluations, … |
|
||||
| Photo | Image de profil (`image_url` / `image.link` selon API) |
|
||||
| Skills | Niveau + pourcentage |
|
||||
| Projets | Terminés **y compris failed** |
|
||||
| Retour | Bouton retour vers vue 1 |
|
||||
| Layout | Responsive (étape 5 peut affiner) |
|
||||
| Exigence | Cette étape |
|
||||
|----------|-------------|
|
||||
| ≥ 4 infos + photo | login (titre / avatar), email, téléphone, niveau cursus, localisation, wallet |
|
||||
| Skills | Niveau entier + **pourcentage** (partie décimale du `level` API) |
|
||||
| Projets | Statuts **`finished`** et **`failed`** |
|
||||
| Erreurs | Réseau, 401, réponse API inattendue — message + réessai |
|
||||
| Token | Un seul OAuth : `IntraAuthService` pour tous les `GET` |
|
||||
| Retour vue 1 | `AppBar` + bouton retour système (`Navigator.pop`) |
|
||||
| Layout | `LayoutBuilder` : colonne sur téléphone, 2 colonnes si largeur ≥ 720 px |
|
||||
|
||||
## API
|
||||
---
|
||||
|
||||
- `GET /v2/users/{id}` avec champs / includes selon apidoc (`cursus_users`, `projects_users`, `skills`, etc.).
|
||||
- Token : toujours via `IntraAuthService`.
|
||||
## Parcours utilisateur (vue 2)
|
||||
|
||||
Guide détaillé à rédiger quand l’étape 3 est codée — demande l’implémentation pas à pas dans le chat.
|
||||
1. Arrivée depuis la recherche avec un objet `user` minimal (`id`, `login`, parfois `image`).
|
||||
2. **Chargement** : appels API en parallèle (spinner plein écran).
|
||||
3. **Succès** : photo, carte « Informations », liste skills, liste projets.
|
||||
4. **Échec** : message rouge + bouton **Réessayer**.
|
||||
5. **Retour** : flèche AppBar → vue 1, nouvelle recherche possible.
|
||||
|
||||
---
|
||||
|
||||
## API 42 utilisée
|
||||
|
||||
Doc : [api.intra.42.fr/apidoc](https://api.intra.42.fr/apidoc)
|
||||
|
||||
| Donnée | Endpoint | Notes |
|
||||
|--------|----------|--------|
|
||||
| Fiche user | *(déjà renvoyée par la recherche étape 3)* | email, phone, location, wallet, image — pas de 2ᵉ `GET /users/{id}` pour limiter le quota |
|
||||
| Fiche complète | `GET /v2/users/{login}` | inclut souvent `cursus_users` + `skills` par cursus |
|
||||
| Niveau / skills | `cursus_users[].skills` | pas `/experiences` (souvent **403** avec token étudiant) |
|
||||
| Projets | `GET /v2/users/{id}/projects_users?page[size]=100` | scope OAuth **`projects`** ; filtrer côté app : `finished` / `failed` |
|
||||
|
||||
Tous les appels :
|
||||
|
||||
```http
|
||||
Authorization: Bearer <access_token>
|
||||
Accept: application/json
|
||||
```
|
||||
|
||||
**Confidentialité :** certains champs peuvent être masqués par l’API selon le profil consulté — afficher `—` si absent (sujet OAuth).
|
||||
|
||||
### Skills — niveau et pourcentage
|
||||
|
||||
L’API renvoie un `level` flottant (ex. `8.42`) :
|
||||
|
||||
- **Niveau affiché** : partie entière (`8`)
|
||||
- **Pourcentage** : partie décimale × 100 (`42 %`)
|
||||
|
||||
---
|
||||
|
||||
## Fichiers (étape 4)
|
||||
|
||||
```
|
||||
lib/
|
||||
├── services/
|
||||
│ ├── intra_auth_service.dart # existant — headers Bearer
|
||||
│ ├── intra_user_service.dart # existant — recherche
|
||||
│ └── intra_profile_service.dart # NEW — agrégation profil
|
||||
└── screens/
|
||||
└── profile_screen.dart # StatefulWidget — chargement + UI
|
||||
```
|
||||
|
||||
### `IntraProfileService`
|
||||
|
||||
- `fetchProfile(Map<String, dynamic> summaryUser)` → `IntraUserProfile`
|
||||
- Requêtes **séquentielles** (pause ~450 ms) + retry sur 429 (`intra_http.dart`)
|
||||
- Cache profil 2 min par `user_id` pour éviter de re-taper l’API au retour arrière
|
||||
- Mêmes exceptions que l’étape 3 : `IntraNetworkException`, `IntraUnauthorizedException`, etc.
|
||||
|
||||
### `ProfileScreen`
|
||||
|
||||
- Reçoit le `user` de l’étape 3
|
||||
- `initState` → `_load()`
|
||||
- Pas de second OAuth
|
||||
|
||||
---
|
||||
|
||||
## Gestion des erreurs (vue 2)
|
||||
|
||||
| Situation | Comportement |
|
||||
|-----------|----------------|
|
||||
| Pas de réseau | Message + **Réessayer** |
|
||||
| Token expiré (401) | « Session expirée… » — reconnecter depuis vue 1 |
|
||||
| Trop de requêtes (429) | Message dédié ; retry automatique côté client + attendre avant de réessayer |
|
||||
| Accès refusé (403) | Scopes `public` + `projects` sur l’app OAuth, puis **reconnexion** dans l’app |
|
||||
| ID manquant | Erreur métier explicite |
|
||||
| Listes vides skills / projets | Texte informatif, pas de crash |
|
||||
|
||||
---
|
||||
|
||||
## Critères « étape 4 terminée »
|
||||
|
||||
| # | Test |
|
||||
|---|------|
|
||||
| 1 | Login valide → photo + au moins 4 champs remplis ou `—` |
|
||||
| 2 | Section skills avec niveau **et** pourcentage |
|
||||
| 3 | Au moins un projet `finished` ou `failed` visible (si l’étudiant en a) |
|
||||
| 4 | Mode avion sur vue 2 → erreur + réessai |
|
||||
| 5 | Retour → vue 1 sans perdre la session OAuth |
|
||||
| 6 | Largeur tablette / fenêtre large → mise en page 2 colonnes |
|
||||
|
||||
---
|
||||
|
||||
## Ensuite — étape 5
|
||||
|
||||
→ Affiner navigation, contraintes et polish responsive sur **les deux vues** (`etape-05-navigation-layout.md` à rédiger).
|
||||
|
||||
@@ -37,11 +37,14 @@ http://localhost:8080/callback
|
||||
|
||||
## Champs d’application (scopes)
|
||||
|
||||
Cocher **uniquement** :
|
||||
Cocher :
|
||||
|
||||
- [x] **Accéder aux données publiques de l'utilisateur** (`public`)
|
||||
- [x] **Accéder aux données des projets de l'utilisateur** (`projects`) — requis pour `projects_users` (sinon **403** sur le profil)
|
||||
|
||||
Laisser **décoché** : équipes / projets, données utilisateur, médias, communauté, sujets.
|
||||
Laisser **décoché** le reste sauf besoin explicite (équipes, médias, forum, etc.).
|
||||
|
||||
Après ajout du scope `projects`, **déconnexion puis nouvelle connexion** dans l’app pour obtenir un token à jour.
|
||||
|
||||
## Après création
|
||||
|
||||
|
||||
Reference in New Issue
Block a user