fix(chat): Correction race condition + Implémentation TODOs

## Corrections Critiques

### Race Condition - Statuts de Messages
- Fix : Les icônes de statut (✓, ✓✓, ✓✓ bleu) ne s'affichaient pas
- Cause : WebSocket delivery confirmations arrivaient avant messages locaux
- Solution : Pattern Optimistic UI dans chat_bloc.dart
  - Création message temporaire immédiate
  - Ajout à la liste AVANT requête HTTP
  - Remplacement par message serveur à la réponse
- Fichier : lib/presentation/state_management/chat_bloc.dart

## Implémentation TODOs (13/21)

### Social (social_header_widget.dart)
-  Copier lien du post dans presse-papiers
-  Partage natif via Share.share()
-  Dialogue de signalement avec 5 raisons

### Partage (share_post_dialog.dart)
-  Interface sélection d'amis avec checkboxes
-  Partage externe via Share API

### Média (media_upload_service.dart)
-  Parsing JSON réponse backend
-  Méthode deleteMedia() pour suppression
-  Génération miniature vidéo

### Posts (create_post_dialog.dart, edit_post_dialog.dart)
-  Extraction URL depuis uploads
-  Documentation chargement médias

### Chat (conversations_screen.dart)
-  Navigation vers notifications
-  ConversationSearchDelegate pour recherche

## Nouveaux Fichiers

### Configuration
- build-prod.ps1 : Script build production avec dart-define
- lib/core/constants/env_config.dart : Gestion environnements

### Documentation
- TODOS_IMPLEMENTED.md : Documentation complète TODOs

## Améliorations

### Architecture
- Refactoring injection de dépendances
- Amélioration routing et navigation
- Optimisation providers (UserProvider, FriendsProvider)

### UI/UX
- Amélioration thème et couleurs
- Optimisation animations
- Meilleure gestion erreurs

### Services
- Configuration API avec env_config
- Amélioration datasources (events, users)
- Optimisation modèles de données
This commit is contained in:
dahoud
2026-01-10 10:43:17 +00:00
parent 06031b01f2
commit 92612abbd7
321 changed files with 43137 additions and 4285 deletions

View File

@@ -1,60 +1,205 @@
import 'package:flutter/material.dart';
/// Classe utilitaire pour gérer les couleurs de l'application en mode clair et sombre.
///
/// Cette classe fournit un système de couleurs cohérent et accessible
/// pour toute l'application, avec support complet du thème clair et sombre.
///
/// **Usage:**
/// ```dart
/// AppColors.primary // Retourne la couleur primaire selon le thème
/// AppColors.lightPrimary // Accès direct à la couleur claire
/// ```
class AppColors {
// Thème clair
static const Color lightPrimary = Color(0xFF0057D9);
static const Color lightSecondary = Color(0xFFFFC107);
// ============================================================================
// THÈME CLAIR - Couleurs pour le mode clair
// ============================================================================
/// Couleur primaire du thème clair (Bleu Instagram-like)
static const Color lightPrimary = Color(0xFF0095F6);
/// Couleur secondaire du thème clair (Rose Instagram-like)
static const Color lightSecondary = Color(0xFFE1306C);
/// Couleur pour le texte/éléments sur la couleur primaire (Blanc)
static const Color lightOnPrimary = Colors.white;
/// Couleur pour le texte/éléments sur la couleur secondaire (Noir)
static const Color lightOnSecondary = Color(0xFF212121);
static const Color lightBackground = Colors.white;
/// Couleur de fond principale (Blanc pur)
static const Color lightBackground = Color(0xFFFAFAFA);
/// Couleur de surface (Blanc)
static const Color lightSurface = Color(0xFFFFFFFF);
/// Couleur de texte primaire (Gris foncé)
static const Color lightTextPrimary = Color(0xFF212121);
/// Couleur de texte secondaire (Gris moyen)
static const Color lightTextSecondary = Color(0xFF616161);
/// Couleur des cartes (Blanc avec ombre douce)
static const Color lightCardColor = Color(0xFFFFFFFF);
/// Couleur d'accent (Vert)
static const Color lightAccentColor = Color(0xFF4CAF50);
/// Couleur d'erreur (Rouge foncé)
static const Color lightError = Color(0xFFB00020);
static const Color lightIconPrimary = Color(0xFF212121); // Icône primaire sombre
static const Color lightIconSecondary = Color(0xFF757575); // Icône secondaire gris clair
// Thème sombre
/// Couleur des icônes primaires (Gris foncé)
static const Color lightIconPrimary = Color(0xFF212121);
/// Couleur des icônes secondaires (Gris clair)
static const Color lightIconSecondary = Color(0xFF757575);
/// Couleur de fond personnalisée (Bleu clair)
static const Color lightBackgroundCustom = Color(0xFFE0F7FA);
// ============================================================================
// THÈME SOMBRE - Couleurs pour le mode sombre
// ============================================================================
/// Couleur primaire du thème sombre (Noir)
static const Color darkPrimary = Color(0xFF121212);
/// Couleur secondaire du thème sombre (Orange)
static const Color darkSecondary = Color(0xFFFF5722);
/// Couleur pour le texte/éléments sur la couleur primaire (Blanc)
static const Color darkOnPrimary = Colors.white;
/// Couleur pour le texte/éléments sur la couleur secondaire (Blanc)
static const Color darkOnSecondary = Colors.white;
/// Couleur de fond principale (Noir)
static const Color darkBackground = Color(0xFF121212);
/// Couleur de surface (Gris très foncé)
static const Color darkSurface = Color(0xFF1F1F1F);
/// Couleur de texte primaire (Gris clair)
static const Color darkTextPrimary = Color(0xFFE0E0E0);
/// Couleur de texte secondaire (Gris moyen)
static const Color darkTextSecondary = Color(0xFFBDBDBD);
/// Couleur des cartes (Gris foncé)
static const Color darkCardColor = Color(0xFF2C2C2C);
/// Couleur d'accent (Vert clair)
static const Color darkAccentColor = Color(0xFF81C784);
/// Couleur d'erreur (Rouge clair)
static const Color darkError = Color(0xFFF1012B);
static const Color darkIconPrimary = Colors.white; // Icône primaire blanche
static const Color darkIconSecondary = Color(0xFFBDBDBD); // Icône secondaire gris clair
// Ajout du background personnalisé
static const Color darkbackgroundCustom = Color(0xFF2C2C3E);
static const Color lightbackgroundCustom = Color(0xFFE0F7FA);
/// Couleur des icônes primaires (Blanc)
static const Color darkIconPrimary = Colors.white;
// Sélection automatique des couleurs en fonction du mode de thème
static Color get primary => isDarkMode() ? darkPrimary : lightPrimary;
static Color get secondary => isDarkMode() ? darkSecondary : lightSecondary;
static Color get onPrimary => isDarkMode() ? darkOnPrimary : lightOnPrimary;
static Color get onSecondary => isDarkMode() ? darkOnSecondary : lightOnSecondary;
static Color get backgroundColor => isDarkMode() ? darkBackground : lightBackground;
static Color get surface => isDarkMode() ? darkSurface : lightSurface;
static Color get textPrimary => isDarkMode() ? darkTextPrimary : lightTextPrimary;
static Color get textSecondary => isDarkMode() ? darkTextSecondary : lightTextSecondary;
static Color get cardColor => isDarkMode() ? darkCardColor : lightCardColor;
static Color get accentColor => isDarkMode() ? darkAccentColor : lightAccentColor;
static Color get errorColor => isDarkMode() ? darkError : lightError;
static Color get iconPrimary => isDarkMode() ? darkIconPrimary : lightIconPrimary;
static Color get iconSecondary => isDarkMode() ? darkIconSecondary : lightIconSecondary;
static Color get customBackgroundColor => isDarkMode() ? darkbackgroundCustom : lightbackgroundCustom;
/// Couleur des icônes secondaires (Gris clair)
static const Color darkIconSecondary = Color(0xFFBDBDBD);
/// Méthode utilitaire pour vérifier si le mode sombre est activé.
static bool isDarkMode() {
final brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
return brightness == Brightness.light;
/// Couleur de fond personnalisée (Bleu foncé)
static const Color darkBackgroundCustom = Color(0xFF2C2C3E);
// ============================================================================
// GETTERS DYNAMIQUES - Retournent la couleur selon le thème actif
// ============================================================================
/// Retourne la couleur primaire selon le thème actif
static Color get primary => _isDarkMode() ? darkPrimary : lightPrimary;
/// Retourne la couleur secondaire selon le thème actif
static Color get secondary => _isDarkMode() ? darkSecondary : lightSecondary;
/// Retourne la couleur pour le texte sur primaire selon le thème actif
static Color get onPrimary => _isDarkMode() ? darkOnPrimary : lightOnPrimary;
/// Retourne la couleur pour le texte sur secondaire selon le thème actif
static Color get onSecondary => _isDarkMode() ? darkOnSecondary : lightOnSecondary;
/// Retourne la couleur de fond selon le thème actif
static Color get backgroundColor => _isDarkMode() ? darkBackground : lightBackground;
/// Retourne la couleur de surface selon le thème actif
static Color get surface => _isDarkMode() ? darkSurface : lightSurface;
/// Retourne la couleur de texte primaire selon le thème actif
static Color get textPrimary => _isDarkMode() ? darkTextPrimary : lightTextPrimary;
/// Retourne la couleur de texte secondaire selon le thème actif
static Color get textSecondary => _isDarkMode() ? darkTextSecondary : lightTextSecondary;
/// Retourne la couleur des cartes selon le thème actif
static Color get cardColor => _isDarkMode() ? darkCardColor : lightCardColor;
/// Retourne la couleur d'accent selon le thème actif
static Color get accentColor => _isDarkMode() ? darkAccentColor : lightAccentColor;
/// Retourne la couleur d'erreur selon le thème actif
static Color get errorColor => _isDarkMode() ? darkError : lightError;
/// Retourne la couleur des icônes primaires selon le thème actif
static Color get iconPrimary => _isDarkMode() ? darkIconPrimary : lightIconPrimary;
/// Retourne la couleur des icônes secondaires selon le thème actif
static Color get iconSecondary => _isDarkMode() ? darkIconSecondary : lightIconSecondary;
/// Retourne la couleur de fond personnalisée selon le thème actif
static Color get customBackgroundColor =>
_isDarkMode() ? darkBackgroundCustom : lightBackgroundCustom;
// ============================================================================
// MÉTHODES UTILITAIRES
// ============================================================================
/// Vérifie si le mode sombre est activé selon les préférences système.
///
/// **Note:** Cette méthode vérifie uniquement les préférences système.
/// Pour vérifier le thème de l'application, utilisez [ThemeProvider].
///
/// Returns `true` si le mode sombre est activé, `false` sinon.
static bool _isDarkMode() {
try {
final brightness =
WidgetsBinding.instance.platformDispatcher.platformBrightness;
return brightness == Brightness.dark;
} catch (e) {
// En cas d'erreur, retourner false (mode clair par défaut)
return false;
}
}
/// Vérifie si le mode sombre est activé (méthode publique).
///
/// Cette méthode est dépréciée. Utilisez [ThemeProvider] pour vérifier
/// le thème de l'application.
@Deprecated('Utilisez ThemeProvider.isDarkMode à la place')
static bool isDarkMode() => _isDarkMode();
/// Crée une couleur avec opacité.
///
/// [color] La couleur de base
/// [opacity] L'opacité entre 0.0 et 1.0
///
/// Returns une nouvelle couleur avec l'opacité spécifiée.
static Color withOpacity(Color color, double opacity) {
return color.withOpacity(opacity.clamp(0.0, 1.0));
}
/// Crée un dégradé linéaire avec les couleurs primaire et secondaire.
///
/// [isDark] Si true, utilise les couleurs du thème sombre
///
/// Returns un [LinearGradient] avec les couleurs appropriées.
static LinearGradient primaryGradient({bool isDark = false}) {
return LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [darkPrimary, darkSecondary]
: [lightPrimary, lightSecondary],
);
}
}

View File

@@ -0,0 +1,326 @@
import 'package:flutter/material.dart';
/// Design System centralisé pour Afterwork
///
/// Ce fichier contient toutes les constantes de design pour assurer
/// une cohérence visuelle à travers toute l'application.
///
/// **Sections:**
/// - Spacing: Espacements standardisés
/// - BorderRadius: Rayons de bordure
/// - Shadows: Ombres et élévations
/// - Durations: Durées d'animations
/// - Curves: Courbes d'animations
/// - Sizes: Tailles standardisées
class DesignSystem {
// ============================================================================
// SPACING
// ============================================================================
/// Espacements standardisés
///
/// Utiliser ces constantes pour tous les paddings, margins, gaps, etc.
/// Cela garantit une cohérence visuelle et facilite les ajustements.
static const double spacing2xs = 2.0;
static const double spacingXs = 4.0;
static const double spacingSm = 8.0;
static const double spacingMd = 12.0;
static const double spacingLg = 16.0;
static const double spacingXl = 24.0;
static const double spacing2xl = 32.0;
static const double spacing3xl = 48.0;
static const double spacing4xl = 64.0;
/// Padding horizontal standard des écrans
static const double screenPaddingHorizontal = spacingLg;
/// Padding vertical standard des écrans
static const double screenPaddingVertical = spacingLg;
/// Gap entre éléments de liste
static const double listItemGap = spacingMd;
/// Gap entre sections
static const double sectionGap = spacingXl;
// ============================================================================
// BORDER RADIUS
// ============================================================================
/// Rayons de bordure standardisés
static const double radiusXs = 4.0;
static const double radiusSm = 8.0;
static const double radiusMd = 12.0;
static const double radiusLg = 16.0;
static const double radiusXl = 20.0;
static const double radius2xl = 24.0;
static const double radiusRound = 999.0;
/// BorderRadius objets pour utilisation directe
static final BorderRadius borderRadiusXs = BorderRadius.circular(radiusXs);
static final BorderRadius borderRadiusSm = BorderRadius.circular(radiusSm);
static final BorderRadius borderRadiusMd = BorderRadius.circular(radiusMd);
static final BorderRadius borderRadiusLg = BorderRadius.circular(radiusLg);
static final BorderRadius borderRadiusXl = BorderRadius.circular(radiusXl);
static final BorderRadius borderRadius2xl = BorderRadius.circular(radius2xl);
static final BorderRadius borderRadiusRound = BorderRadius.circular(radiusRound);
// ============================================================================
// SHADOWS
// ============================================================================
/// Ombres standardisées pour Material Design
///
/// Niveaux d'élévation:
/// - None: Pas d'ombre
/// - Sm: Petite élévation (cartes au repos)
/// - Md: Élévation moyenne (cartes survolées)
/// - Lg: Grande élévation (dialogs, bottom sheets)
/// - Xl: Très grande élévation (navigation drawer)
static const List<BoxShadow> shadowNone = [];
static const List<BoxShadow> shadowSm = [
BoxShadow(
color: Color(0x0F000000), // 6% opacity
blurRadius: 4,
offset: Offset(0, 1),
spreadRadius: 0,
),
];
static const List<BoxShadow> shadowMd = [
BoxShadow(
color: Color(0x14000000), // 8% opacity
blurRadius: 8,
offset: Offset(0, 2),
spreadRadius: 0,
),
];
static const List<BoxShadow> shadowLg = [
BoxShadow(
color: Color(0x1F000000), // 12% opacity
blurRadius: 16,
offset: Offset(0, 4),
spreadRadius: 0,
),
];
static const List<BoxShadow> shadowXl = [
BoxShadow(
color: Color(0x29000000), // 16% opacity
blurRadius: 24,
offset: Offset(0, 8),
spreadRadius: 0,
),
];
/// Ombres pour mode sombre (plus subtiles)
static const List<BoxShadow> shadowSmDark = [
BoxShadow(
color: Color(0x33000000), // 20% opacity
blurRadius: 4,
offset: Offset(0, 1),
spreadRadius: 0,
),
];
static const List<BoxShadow> shadowMdDark = [
BoxShadow(
color: Color(0x3D000000), // 24% opacity
blurRadius: 8,
offset: Offset(0, 2),
spreadRadius: 0,
),
];
static const List<BoxShadow> shadowLgDark = [
BoxShadow(
color: Color(0x47000000), // 28% opacity
blurRadius: 16,
offset: Offset(0, 4),
spreadRadius: 0,
),
];
// ============================================================================
// DURATIONS (Durées d'animations)
// ============================================================================
/// Durées d'animations standardisées
///
/// Suivent les Material Design motion guidelines:
/// - Fast: Micro-interactions rapides (100-200ms)
/// - Medium: Transitions standard (200-300ms)
/// - Slow: Animations complexes (300-500ms)
static const Duration durationInstant = Duration(milliseconds: 100);
static const Duration durationFast = Duration(milliseconds: 200);
static const Duration durationMedium = Duration(milliseconds: 300);
static const Duration durationSlow = Duration(milliseconds: 400);
static const Duration durationSlower = Duration(milliseconds: 500);
// ============================================================================
// CURVES (Courbes d'animations)
// ============================================================================
/// Courbes d'animations standardisées
///
/// Material Design recommande:
/// - easeIn: Accélération au début (sortie d'écran)
/// - easeOut: Décélération à la fin (entrée d'écran)
/// - easeInOut: Accélération puis décélération (transitions)
/// - bounce: Effet rebond (micro-interactions fun)
static const Curve curveStandard = Curves.easeInOut;
static const Curve curveDecelerate = Curves.easeOut;
static const Curve curveAccelerate = Curves.easeIn;
static const Curve curveSharp = Curves.easeInOutCubic;
static const Curve curveBounce = Curves.elasticOut;
// ============================================================================
// SIZES (Tailles standardisées)
// ============================================================================
/// Tailles d'icônes
static const double iconSizeXs = 16.0;
static const double iconSizeSm = 20.0;
static const double iconSizeMd = 24.0;
static const double iconSizeLg = 32.0;
static const double iconSizeXl = 48.0;
static const double iconSize2xl = 64.0;
/// Tailles d'avatars
static const double avatarSizeXs = 24.0;
static const double avatarSizeSm = 32.0;
static const double avatarSizeMd = 40.0;
static const double avatarSizeLg = 56.0;
static const double avatarSizeXl = 72.0;
static const double avatarSize2xl = 96.0;
/// Hauteurs de boutons
static const double buttonHeightSm = 36.0;
static const double buttonHeightMd = 44.0;
static const double buttonHeightLg = 52.0;
/// Hauteurs de champs de saisie
static const double inputHeightSm = 40.0;
static const double inputHeightMd = 48.0;
static const double inputHeightLg = 56.0;
/// Tailles de FAB (Floating Action Button)
static const double fabSizeSm = 48.0;
static const double fabSizeMd = 56.0;
static const double fabSizeLg = 64.0;
// ============================================================================
// OPACITIES (Opacités standardisées)
// ============================================================================
static const double opacityDisabled = 0.38;
static const double opacityInactive = 0.54;
static const double opacitySecondary = 0.7;
static const double opacityPrimary = 0.87;
static const double opacityFull = 1.0;
// ============================================================================
// Z-INDEX / ELEVATION
// ============================================================================
static const double elevationNone = 0.0;
static const double elevationXs = 1.0;
static const double elevationSm = 2.0;
static const double elevationMd = 4.0;
static const double elevationLg = 8.0;
static const double elevationXl = 16.0;
// ============================================================================
// BREAKPOINTS (pour responsive design)
// ============================================================================
static const double breakpointMobile = 600.0;
static const double breakpointTablet = 900.0;
static const double breakpointDesktop = 1200.0;
// ============================================================================
// HELPER METHODS
// ============================================================================
/// Retourne les ombres appropriées selon le thème
static List<BoxShadow> getShadow(
BuildContext context,
ShadowSize size,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
switch (size) {
case ShadowSize.none:
return shadowNone;
case ShadowSize.sm:
return isDark ? shadowSmDark : shadowSm;
case ShadowSize.md:
return isDark ? shadowMdDark : shadowMd;
case ShadowSize.lg:
return isDark ? shadowLgDark : shadowLg;
case ShadowSize.xl:
return shadowXl;
}
}
/// Retourne un EdgeInsets avec padding uniforme
static EdgeInsets paddingAll(double value) => EdgeInsets.all(value);
/// Retourne un EdgeInsets avec padding horizontal
static EdgeInsets paddingHorizontal(double value) =>
EdgeInsets.symmetric(horizontal: value);
/// Retourne un EdgeInsets avec padding vertical
static EdgeInsets paddingVertical(double value) =>
EdgeInsets.symmetric(vertical: value);
/// Retourne un EdgeInsets avec padding screen standard
static EdgeInsets get paddingScreen => const EdgeInsets.symmetric(
horizontal: screenPaddingHorizontal,
vertical: screenPaddingVertical,
);
/// Retourne un SizedBox avec hauteur
static SizedBox verticalSpace(double height) => SizedBox(height: height);
/// Retourne un SizedBox avec largeur
static SizedBox horizontalSpace(double width) => SizedBox(width: width);
}
/// Énumération pour les tailles d'ombres
enum ShadowSize {
none,
sm,
md,
lg,
xl,
}
/// Extensions pour faciliter l'utilisation du Design System
extension DesignSystemExtensions on BuildContext {
/// Retourne les ombres selon le thème
List<BoxShadow> shadow(ShadowSize size) => DesignSystem.getShadow(this, size);
/// Retourne true si on est en mode sombre
bool get isDarkMode => Theme.of(this).brightness == Brightness.dark;
/// Retourne la largeur de l'écran
double get screenWidth => MediaQuery.of(this).size.width;
/// Retourne la hauteur de l'écran
double get screenHeight => MediaQuery.of(this).size.height;
/// Retourne true si on est sur mobile
bool get isMobile => screenWidth < DesignSystem.breakpointMobile;
/// Retourne true si on est sur tablette
bool get isTablet =>
screenWidth >= DesignSystem.breakpointMobile &&
screenWidth < DesignSystem.breakpointTablet;
/// Retourne true si on est sur desktop
bool get isDesktop => screenWidth >= DesignSystem.breakpointDesktop;
}

View File

@@ -0,0 +1,209 @@
/// Exception levée lorsque la configuration de l'environnement est invalide.
class ConfigurationException implements Exception {
ConfigurationException(this.message);
final String message;
@override
String toString() => 'ConfigurationException: $message';
}
/// Configuration centralisée de l'environnement de l'application.
///
/// Ce fichier gère toutes les variables d'environnement et secrets
/// de l'application de manière sécurisée. Les valeurs peuvent être
/// définies au moment du build via des variables d'environnement.
///
/// **Usage en développement:**
/// ```dart
/// final apiUrl = EnvConfig.apiBaseUrl; // Utilise la valeur par défaut
/// ```
///
/// **Usage en production:**
/// ```bash
/// flutter build apk --dart-define=API_BASE_URL=https://api.example.com
/// ```
///
/// **Validation:**
/// ```dart
/// // Valider au démarrage de l'application
/// EnvConfig.validate(throwOnError: true);
/// ```
class EnvConfig {
/// Constructeur privé pour empêcher l'instanciation
EnvConfig._();
// ============================================================================
// CONFIGURATION API
// ============================================================================
/// URL de base de l'API backend.
///
/// Cette valeur peut être définie au moment du build avec:
/// `--dart-define=API_BASE_URL=https://api.example.com`
///
/// **Valeur par défaut:** `http://192.168.1.145:8080` (développement)
static const String apiBaseUrl = String.fromEnvironment(
'API_BASE_URL',
defaultValue: 'http://192.168.1.145:8080',
);
/// Timeout pour les requêtes réseau (en secondes).
///
/// **Valeur par défaut:** 30 secondes
static const int networkTimeout = int.fromEnvironment(
'NETWORK_TIMEOUT',
defaultValue: 30,
);
// ============================================================================
// ENVIRONNEMENT
// ============================================================================
/// Environnement actuel de l'application.
///
/// Valeurs possibles: `development`, `staging`, `production`
///
/// **Valeur par défaut:** `development`
static const String environment = String.fromEnvironment(
'ENVIRONMENT',
defaultValue: 'development',
);
/// Vérifie si l'environnement est en production.
///
/// Returns `true` si l'environnement est `production`, `false` sinon.
static bool get isProduction => environment == 'production';
/// Vérifie si l'environnement est en développement.
///
/// Returns `true` si l'environnement est `development`, `false` sinon.
static bool get isDevelopment => environment == 'development';
/// Vérifie si l'environnement est en staging.
///
/// Returns `true` si l'environnement est `staging`, `false` sinon.
static bool get isStaging => environment == 'staging';
// ============================================================================
// CONFIGURATION DEBUG
// ============================================================================
/// Mode debug activé.
///
/// Quand activé, des logs supplémentaires sont affichés et certaines
/// fonctionnalités de débogage sont disponibles.
///
/// **Valeur par défaut:** `true`
static const bool isDebugMode = bool.fromEnvironment(
'DEBUG_MODE',
defaultValue: true,
);
/// Active les logs détaillés.
///
/// **Valeur par défaut:** `true` en développement, `false` en production
static bool get enableDetailedLogs => isDevelopment || isDebugMode;
// ============================================================================
// SERVICES EXTERNES
// ============================================================================
/// Clé API Google Maps (si nécessaire).
///
/// Cette valeur doit être définie au moment du build avec:
/// `--dart-define=GOOGLE_MAPS_API_KEY=your_api_key`
///
/// **Note:** Ne jamais commiter cette clé dans le code source.
static const String googleMapsApiKey = String.fromEnvironment(
'GOOGLE_MAPS_API_KEY',
);
// ============================================================================
// MÉTHODES UTILITAIRES
// ============================================================================
/// Valide que la configuration est correcte.
///
/// Cette méthode vérifie que toutes les valeurs requises sont définies
/// et valides pour l'environnement actuel.
///
/// Throws [ConfigurationException] si la validation échoue en production.
/// Returns `true` si la configuration est valide, `false` sinon en développement.
///
/// **Validations effectuées:**
/// - URL API non vide et format valide
/// - HTTPS obligatoire en production
/// - Clés API requises en production
/// - Timeout réseau valide (> 0)
static bool validate({bool throwOnError = false}) {
final errors = <String>[];
// Validation de l'URL API
if (apiBaseUrl.isEmpty) {
errors.add('API_BASE_URL ne peut pas être vide');
} else {
try {
final uri = Uri.parse(apiBaseUrl);
if (!uri.hasScheme || (!uri.scheme.startsWith('http'))) {
errors.add('API_BASE_URL doit être une URL HTTP/HTTPS valide');
}
} catch (e) {
errors.add('API_BASE_URL n\'est pas une URL valide: $e');
}
}
// Validation HTTPS en production
if (isProduction && !apiBaseUrl.startsWith('https://')) {
errors.add('API_BASE_URL doit utiliser HTTPS en production');
}
// Validation du timeout réseau
if (networkTimeout <= 0) {
errors.add('NETWORK_TIMEOUT doit être supérieur à 0');
}
// Validation des clés API en production (si nécessaire)
if (isProduction) {
// Google Maps API Key est optionnelle mais recommandée si on utilise Google Maps
// On ne force pas car elle peut ne pas être nécessaire selon les fonctionnalités
}
// Si des erreurs sont trouvées
if (errors.isNotEmpty) {
final errorMessage = 'Erreurs de configuration:\n${errors.join('\n')}';
if (throwOnError || isProduction) {
throw ConfigurationException(errorMessage);
}
// En développement, on log juste les erreurs
if (isDevelopment) {
// Utiliser print car AppLogger pourrait ne pas être initialisé
print('[EnvConfig] ⚠️ $errorMessage');
}
return false;
}
return true;
}
/// Retourne un résumé de la configuration actuelle.
///
/// Cette méthode est utile pour le débogage et les logs.
///
/// **Note:** Les valeurs sensibles (comme les clés API) ne sont pas incluses.
///
/// Returns une chaîne décrivant la configuration actuelle.
static String getConfigSummary() {
return '''
Environment: $environment
API Base URL: $apiBaseUrl
Network Timeout: ${networkTimeout}s
Debug Mode: $isDebugMode
Google Maps API Key: ${googleMapsApiKey.isNotEmpty ? '***configured***' : 'not configured'}
''';
}
}

View File

@@ -140,6 +140,12 @@ class Urls {
static String getEventsByUserWithUserId(String userId) =>
'$eventsBase/user/$userId';
/// Retourne l'URL pour obtenir les événements de l'utilisateur et de ses amis
///
/// [userId] L'ID de l'utilisateur
static String getEventsByFriends(String userId) =>
'$eventsBase/friends/$userId';
/// Endpoint pour rechercher des événements
///
/// **Note:** Utilisez des paramètres de requête pour le mot-clé
@@ -244,6 +250,13 @@ class Urls {
static String rejectFriendRequestWithId(String friendshipId) =>
'$friendsBase/$friendshipId/reject';
/// Retourne l'URL pour récupérer les suggestions d'amis
///
/// [userId] L'ID de l'utilisateur
/// [limit] Nombre maximum de suggestions (optionnel, par défaut 10)
static String getFriendSuggestionsWithUserId(String userId, {int limit = 10}) =>
'$friendsBase/suggestions/$userId?limit=$limit';
// ============================================================================
// NOTIFICATIONS
// ============================================================================
@@ -320,6 +333,12 @@ class Urls {
static String commentSocialPostWithId(String postId) =>
'$postsBase/$postId/comment';
/// Retourne l'URL pour obtenir tous les commentaires d'un post
///
/// [postId] L'ID du post
static String getCommentsForPost(String postId) =>
'$postsBase/$postId/comments';
/// Retourne l'URL pour partager un post
///
/// [postId] L'ID du post
@@ -331,6 +350,124 @@ class Urls {
static String getSocialPostsByUserId(String userId) =>
'$postsBase/user/$userId';
/// Retourne l'URL pour obtenir les posts de l'utilisateur et de ses amis
///
/// [userId] L'ID de l'utilisateur
static String getSocialPostsByFriends(String userId) =>
'$postsBase/friends/$userId';
// ============================================================================
// STORIES
// ============================================================================
/// Endpoint de base pour les opérations sur les stories
static String get storiesBase => '$baseUrl/stories';
/// Retourne l'URL pour obtenir toutes les stories (actives)
static String get getAllStories => storiesBase;
/// Retourne l'URL pour obtenir les stories d'un utilisateur
///
/// [userId] L'ID de l'utilisateur
static String getStoriesByUserId(String userId) => '$storiesBase/user/$userId';
/// Retourne l'URL pour créer une nouvelle story
static String get createStory => storiesBase;
/// Retourne l'URL pour obtenir une story par son ID
///
/// [storyId] L'ID de la story
static String getStoryByIdWithId(String storyId) => '$storiesBase/$storyId';
/// Retourne l'URL pour supprimer une story
///
/// [storyId] L'ID de la story
static String deleteStoryWithId(String storyId) => '$storiesBase/$storyId';
/// Retourne l'URL pour marquer une story comme vue
///
/// [storyId] L'ID de la story
/// [userId] L'ID de l'utilisateur qui voit la story
static String markStoryAsViewedWithId(String storyId, String userId) =>
'$storiesBase/$storyId/view?userId=$userId';
/// Retourne l'URL pour obtenir les vues d'une story
///
/// [storyId] L'ID de la story
static String getStoryViewsWithId(String storyId) => '$storiesBase/$storyId/views';
// ============================================================================
// MESSAGERIE
// ============================================================================
/// Endpoint de base pour les opérations sur les messages
static String get messagesBase => '$baseUrl/messages';
/// Retourne l'URL pour envoyer un message
static String get sendMessage => messagesBase;
/// Retourne l'URL pour obtenir les conversations d'un utilisateur
///
/// [userId] L'ID de l'utilisateur
static String getUserConversations(String userId) =>
'$messagesBase/conversations/$userId';
/// Retourne l'URL pour obtenir les messages d'une conversation
///
/// [conversationId] L'ID de la conversation
/// [page] Le numéro de la page (optionnel)
/// [size] La taille de la page (optionnel)
static String getConversationMessages(String conversationId,
{int page = 0, int size = 50}) =>
'$messagesBase/conversation/$conversationId?page=$page&size=$size';
/// Retourne l'URL pour obtenir une conversation entre deux utilisateurs
///
/// [user1Id] L'ID du premier utilisateur
/// [user2Id] L'ID du deuxième utilisateur
static String getConversationBetweenUsers(String user1Id, String user2Id) =>
'$messagesBase/conversation/between/$user1Id/$user2Id';
/// Retourne l'URL pour marquer un message comme lu
///
/// [messageId] L'ID du message
static String markMessageAsRead(String messageId) =>
'$messagesBase/$messageId/read';
/// Retourne l'URL pour marquer tous les messages d'une conversation comme lus
///
/// [conversationId] L'ID de la conversation
/// [userId] L'ID de l'utilisateur
static String markAllMessagesAsRead(String conversationId, String userId) =>
'$messagesBase/conversation/$conversationId/read/$userId';
/// Retourne l'URL pour obtenir le nombre de messages non lus
///
/// [userId] L'ID de l'utilisateur
static String getUnreadMessagesCount(String userId) =>
'$messagesBase/unread/count/$userId';
/// Retourne l'URL pour supprimer un message
///
/// [messageId] L'ID du message
static String deleteMessage(String messageId) => '$messagesBase/$messageId';
/// Retourne l'URL pour supprimer une conversation
///
/// [conversationId] L'ID de la conversation
static String deleteConversation(String conversationId) =>
'$messagesBase/conversation/$conversationId';
/// Retourne l'URL WebSocket pour le chat en temps réel
///
/// [userId] L'ID de l'utilisateur
static String getChatWebSocketUrl(String userId) {
final wsUrl = baseUrl
.replaceFirst('http://', 'ws://')
.replaceFirst('https://', 'wss://');
return '$wsUrl/chat/ws/$userId';
}
// ============================================================================
// MÉTHODES UTILITAIRES
// ============================================================================