lot4
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// Point d'entrée : charge les secrets depuis `.env` (asset, non versionné) puis vue recherche.
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import '../services/intra_api_exception.dart';
|
||||
import '../services/intra_profile_service.dart';
|
||||
import '../theme/swifty_ui.dart';
|
||||
|
||||
/// Vue 2 — profil complet (étape 4).
|
||||
/// Vue 2 — photo, ≥ 4 champs, skills (niveau + %), projets validés et failed.
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
const ProfileScreen({super.key, required this.user});
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||
loading: _searching,
|
||||
onPressed: _search,
|
||||
),
|
||||
// Bonus refresh : invalide l'access token en mémoire (build debug uniquement).
|
||||
if (kDebugMode) ...[
|
||||
const SizedBox(height: 12),
|
||||
TextButton(
|
||||
|
||||
@@ -139,8 +139,7 @@ class IntraAuthService {
|
||||
_refreshToken = refresh;
|
||||
}
|
||||
|
||||
/// Simule un access token expiré (garde le refresh) — tests bonus en debug.
|
||||
@visibleForTesting
|
||||
/// Simule un access token expiré (garde le refresh) — démo bonus, appelé depuis l’UI en `kDebugMode`.
|
||||
void debugInvalidateAccessToken() {
|
||||
if (!canRefresh) return;
|
||||
_accessToken = 'invalid_access_token_for_refresh_test';
|
||||
|
||||
@@ -50,6 +50,7 @@ Duration _retryDelay(http.Response response, int attempt) {
|
||||
return Duration(milliseconds: ms.clamp(800, 8000));
|
||||
}
|
||||
|
||||
/// Lance une [IntraApiException] si le code HTTP n'est pas 200.
|
||||
void ensureIntraOk(http.Response response) {
|
||||
if (response.statusCode == 401) {
|
||||
throw const IntraUnauthorizedException();
|
||||
@@ -67,6 +68,7 @@ void ensureIntraOk(http.Response response) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Décode le corps JSON après contrôle du statut HTTP.
|
||||
dynamic decodeIntraBody(http.Response response) {
|
||||
ensureIntraOk(response);
|
||||
return jsonDecode(response.body);
|
||||
|
||||
@@ -55,10 +55,12 @@ class _ProfileCacheEntry {
|
||||
final DateTime fetchedAt;
|
||||
}
|
||||
|
||||
/// Agrège fiche user, skills (cursus, pas `/experiences`) et `projects_users` (scope `projects`).
|
||||
class IntraProfileService {
|
||||
IntraProfileService._();
|
||||
static final IntraProfileService instance = IntraProfileService._();
|
||||
|
||||
/// Cache court + délai entre GET pour limiter les 429 sur l'API 42.
|
||||
static const Duration _cacheTtl = Duration(minutes: 2);
|
||||
static const Duration _betweenRequests = Duration(milliseconds: 450);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'intra_api_exception.dart';
|
||||
import 'intra_auth_service.dart';
|
||||
import 'intra_http.dart';
|
||||
|
||||
/// Recherche d'un login via `GET /v2/users?filter[login]=` (même token OAuth que le profil).
|
||||
class IntraUserService {
|
||||
IntraUserService._();
|
||||
static final IntraUserService instance = IntraUserService._();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Constantes UI partagées (étape 5 — layout).
|
||||
/// Constantes UI partagées (layout responsive, thème 42).
|
||||
abstract final class SwiftyUi {
|
||||
static const Color brand = Color(0xFF00BABC);
|
||||
|
||||
@@ -66,6 +66,7 @@ class ResponsiveScrollBody extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Message d'erreur centré (recherche ou profil).
|
||||
class ErrorMessage extends StatelessWidget {
|
||||
const ErrorMessage({super.key, required this.message});
|
||||
|
||||
@@ -81,6 +82,7 @@ class ErrorMessage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Bouton principal avec indicateur de chargement.
|
||||
class LoadingFilledButton extends StatelessWidget {
|
||||
const LoadingFilledButton({
|
||||
super.key,
|
||||
|
||||
Reference in New Issue
Block a user