Release v3.1.4 - Mode terrain et génération PDF #10
@@ -9,6 +9,7 @@
|
||||
5. [Base de données](#base-de-données)
|
||||
6. [Sécurité](#sécurité)
|
||||
7. [Endpoints API](#endpoints-api)
|
||||
8. [Changements récents](#changements-récents)
|
||||
|
||||
## Structure du projet
|
||||
|
||||
@@ -126,6 +127,54 @@ Exemple détaillé du parcours d'une requête POST /api/users :
|
||||
- Gère le pool de connexions
|
||||
- Assure la sécurité des requêtes
|
||||
|
||||
## Base de données
|
||||
|
||||
### Structure des tables principales
|
||||
|
||||
#### Table `users`
|
||||
- `encrypted_user_name` : Identifiant de connexion chiffré (unique)
|
||||
- `encrypted_email` : Email chiffré (unique)
|
||||
- `user_pass_hash` : Hash du mot de passe
|
||||
- `encrypted_name`, `encrypted_phone`, `encrypted_mobile` : Données personnelles chiffrées
|
||||
- Autres champs : `first_name`, `sect_name`, `fk_role`, `fk_entite`, etc.
|
||||
|
||||
#### Table `entites` (Amicales)
|
||||
- `chk_mdp_manuel` (DEFAULT 0) : Gestion manuelle des mots de passe
|
||||
- `chk_username_manuel` (DEFAULT 0) : Gestion manuelle des identifiants
|
||||
- `chk_stripe` : Activation des paiements Stripe
|
||||
- Données chiffrées : `encrypted_name`, `encrypted_email`, `encrypted_phone`, etc.
|
||||
|
||||
#### Table `medias`
|
||||
- `support` : Type de support (entite, user, operation, passage)
|
||||
- `support_id` : ID de l'élément associé
|
||||
- `file_category` : Catégorie (logo, export, carte, etc.)
|
||||
- `file_path` : Chemin complet du fichier
|
||||
- `processed_width/height` : Dimensions après traitement
|
||||
- Utilisée pour stocker les logos des entités
|
||||
|
||||
### Chiffrement des données
|
||||
|
||||
Toutes les données sensibles sont chiffrées avec AES-256-CBC :
|
||||
- Emails, noms, téléphones
|
||||
- Identifiants de connexion
|
||||
- Informations bancaires (IBAN, BIC)
|
||||
|
||||
### Migration de base de données
|
||||
|
||||
Script SQL pour ajouter les nouveaux champs :
|
||||
|
||||
```sql
|
||||
-- Ajout de la gestion manuelle des usernames
|
||||
ALTER TABLE `entites`
|
||||
ADD COLUMN `chk_username_manuel` tinyint(1) unsigned NOT NULL DEFAULT 0
|
||||
COMMENT 'Gestion des usernames manuelle (1) ou automatique (0)'
|
||||
AFTER `chk_mdp_manuel`;
|
||||
|
||||
-- Index pour optimiser la vérification d'unicité
|
||||
ALTER TABLE `users`
|
||||
ADD INDEX `idx_encrypted_user_name` (`encrypted_user_name`);
|
||||
```
|
||||
|
||||
## Sécurité
|
||||
|
||||
### Mesures implémentées
|
||||
@@ -137,6 +186,8 @@ Exemple détaillé du parcours d'une requête POST /api/users :
|
||||
- Gestion des CORS
|
||||
- Session sécurisée
|
||||
- Authentification requise
|
||||
- Chiffrement AES-256 des données sensibles
|
||||
- Envoi séparé des identifiants par email
|
||||
|
||||
## Endpoints API
|
||||
|
||||
@@ -223,39 +274,260 @@ La configuration des sessions inclut :
|
||||
|
||||
#### Création d'utilisateur
|
||||
|
||||
La création d'utilisateur s'adapte aux paramètres de l'entité (amicale) :
|
||||
|
||||
```http
|
||||
POST /api/users
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {session_id}
|
||||
|
||||
{
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"password": "SecurePassword123"
|
||||
"name": "John Doe",
|
||||
"first_name": "John",
|
||||
"role": 1,
|
||||
"fk_entite": 5,
|
||||
"username": "j.doe38", // Requis si chk_username_manuel=1 pour l'entité
|
||||
"password": "SecurePass123", // Requis si chk_mdp_manuel=1 pour l'entité
|
||||
"phone": "0476123456",
|
||||
"mobile": "0612345678",
|
||||
"sect_name": "Secteur A",
|
||||
"date_naissance": "1990-01-15",
|
||||
"date_embauche": "2020-03-01"
|
||||
}
|
||||
```
|
||||
|
||||
**Comportement selon les paramètres de l'entité :**
|
||||
|
||||
| chk_username_manuel | chk_mdp_manuel | Comportement |
|
||||
|---------------------|----------------|--------------|
|
||||
| 0 | 0 | Username et password générés automatiquement |
|
||||
| 0 | 1 | Username généré, password requis dans le payload |
|
||||
| 1 | 0 | Username requis dans le payload, password généré |
|
||||
| 1 | 1 | Username et password requis dans le payload |
|
||||
|
||||
**Validation du username (si manuel) :**
|
||||
- Format : 10-30 caractères
|
||||
- Commence par une lettre
|
||||
- Caractères autorisés : a-z, 0-9, ., -, _
|
||||
- Doit être unique dans toute la base
|
||||
|
||||
**Réponse réussie :**
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Utilisateur créé",
|
||||
"id": "123"
|
||||
"status": "success",
|
||||
"message": "Utilisateur créé avec succès",
|
||||
"id": 123,
|
||||
"username": "j.doe38", // Toujours retourné
|
||||
"password": "xY7#mK9@pL2" // Retourné seulement si généré automatiquement
|
||||
}
|
||||
```
|
||||
|
||||
**Envoi d'emails :**
|
||||
- **Email 1** : Identifiant de connexion (toujours envoyé)
|
||||
- **Email 2** : Mot de passe (toujours envoyé, 1 seconde après le premier)
|
||||
|
||||
**Codes de statut :**
|
||||
|
||||
- 201: Création réussie
|
||||
- 400: Données invalides
|
||||
- 400: Données invalides ou username/password manquant si requis
|
||||
- 401: Non authentifié
|
||||
- 403: Accès non autorisé (rôle insuffisant)
|
||||
- 409: Email ou username déjà utilisé
|
||||
- 500: Erreur serveur
|
||||
|
||||
#### Vérification de disponibilité du username
|
||||
|
||||
```http
|
||||
POST /api/users/check-username
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {session_id}
|
||||
|
||||
{
|
||||
"username": "j.doe38"
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse si disponible :**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"available": true,
|
||||
"message": "Nom d'utilisateur disponible",
|
||||
"username": "j.doe38"
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse si déjà pris :**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"available": false,
|
||||
"message": "Ce nom d'utilisateur est déjà utilisé",
|
||||
"suggestions": ["j.doe38_42", "j.doe381234", "j.doe3825"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Autres endpoints
|
||||
|
||||
- GET /api/users
|
||||
- GET /api/users/{id}
|
||||
- PUT /api/users/{id}
|
||||
- DELETE /api/users/{id}
|
||||
- POST /api/users/{id}/reset-password
|
||||
|
||||
### Entités (Amicales)
|
||||
|
||||
#### Upload du logo d'une entité
|
||||
|
||||
```http
|
||||
POST /api/entites/{id}/logo
|
||||
Content-Type: multipart/form-data
|
||||
Authorization: Bearer {session_id}
|
||||
|
||||
Body:
|
||||
logo: File (image/png, image/jpeg, image/jpg)
|
||||
```
|
||||
|
||||
**Restrictions :**
|
||||
- Réservé aux administrateurs d'amicale (fk_role == 2)
|
||||
- L'admin ne peut uploader que le logo de sa propre amicale
|
||||
- Un seul logo actif par entité (le nouveau remplace l'ancien)
|
||||
|
||||
**Traitement de l'image :**
|
||||
- Formats acceptés : PNG, JPG, JPEG
|
||||
- Redimensionnement automatique : 250x250px maximum (ratio conservé)
|
||||
- Résolution : 72 DPI (standard web)
|
||||
- Préservation de la transparence pour les PNG
|
||||
|
||||
**Stockage :**
|
||||
- Chemin : `/uploads/entites/{id}/logo/logo_{id}_{timestamp}.{ext}`
|
||||
- Enregistrement dans la table `medias`
|
||||
- Suppression automatique de l'ancien logo
|
||||
|
||||
**Réponse réussie :**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Logo uploadé avec succès",
|
||||
"media_id": 42,
|
||||
"file_name": "logo_5_1234567890.jpg",
|
||||
"file_path": "/entites/5/logo/logo_5_1234567890.jpg",
|
||||
"dimensions": {
|
||||
"width": 250,
|
||||
"height": 180
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Récupération du logo d'une entité
|
||||
|
||||
```http
|
||||
GET /api/entites/{id}/logo
|
||||
Authorization: Bearer {session_id}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"logo": {
|
||||
"id": 42,
|
||||
"data_url": "data:image/png;base64,iVBORw0KGgoAAAANS...",
|
||||
"file_name": "logo_5_1234567890.png",
|
||||
"mime_type": "image/png",
|
||||
"width": 250,
|
||||
"height": 180,
|
||||
"size": 15234
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note :** Le logo est également inclus automatiquement dans la réponse du login si disponible.
|
||||
|
||||
#### Mise à jour d'une entité
|
||||
|
||||
```http
|
||||
PUT /api/entites/{id}
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {session_id}
|
||||
|
||||
{
|
||||
"name": "Amicale de Grenoble",
|
||||
"adresse1": "123 rue de la Caserne",
|
||||
"adresse2": "",
|
||||
"code_postal": "38000",
|
||||
"ville": "Grenoble",
|
||||
"phone": "0476123456",
|
||||
"mobile": "0612345678",
|
||||
"email": "contact@amicale38.fr",
|
||||
"chk_stripe": true, // Activation paiement Stripe
|
||||
"chk_mdp_manuel": false, // Génération auto des mots de passe
|
||||
"chk_username_manuel": false, // Génération auto des usernames
|
||||
"chk_copie_mail_recu": true,
|
||||
"chk_accept_sms": false
|
||||
}
|
||||
```
|
||||
|
||||
**Paramètres de gestion des membres :**
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| chk_mdp_manuel | boolean | `true`: L'admin saisit les mots de passe<br>`false`: Génération automatique |
|
||||
| chk_username_manuel | boolean | `true`: L'admin saisit les identifiants<br>`false`: Génération automatique |
|
||||
| chk_stripe | boolean | Active/désactive les paiements Stripe |
|
||||
|
||||
**Note :** Ces paramètres sont modifiables uniquement par les administrateurs (fk_role > 1).
|
||||
|
||||
#### Réponse du login avec paramètres entité
|
||||
|
||||
Lors du login, les paramètres de l'entité sont retournés dans le groupe `amicale` :
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"session_id": "abc123...",
|
||||
"session_expiry": "2025-01-09T15:30:00+00:00",
|
||||
"user": {
|
||||
"id": 9999980,
|
||||
"fk_entite": 5,
|
||||
"fk_role": 2,
|
||||
"fk_titre": null,
|
||||
"first_name": "Pierre",
|
||||
"sect_name": "",
|
||||
"date_naissance": "1990-01-15", // Maintenant correctement récupéré
|
||||
"date_embauche": "2020-03-01", // Maintenant correctement récupéré
|
||||
"username": "pv_admin",
|
||||
"name": "VALERY ADM",
|
||||
"phone": "0476123456", // Maintenant correctement récupéré
|
||||
"mobile": "0612345678", // Maintenant correctement récupéré
|
||||
"email": "contact@resalice.com"
|
||||
},
|
||||
"amicale": {
|
||||
"id": 5,
|
||||
"name": "Amicale de Grenoble",
|
||||
"chk_mdp_manuel": 0,
|
||||
"chk_username_manuel": 0,
|
||||
"chk_stripe": 1,
|
||||
"logo": { // Logo de l'entité (si disponible)
|
||||
"id": 42,
|
||||
"data_url": "data:image/png;base64,iVBORw0KGgoAAAANS...",
|
||||
"file_name": "logo_5_1234567890.png",
|
||||
"mime_type": "image/png",
|
||||
"width": 250,
|
||||
"height": 180
|
||||
}
|
||||
// ... autres champs
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ces paramètres permettent à l'application Flutter d'adapter dynamiquement le formulaire de création de membre.
|
||||
|
||||
## Intégration Frontend
|
||||
|
||||
@@ -298,3 +570,45 @@ fetch('/api/endpoint', {
|
||||
- Surveillance de la base de données
|
||||
- Monitoring des performances
|
||||
- Alertes sur erreurs critiques
|
||||
|
||||
## Changements récents
|
||||
|
||||
### Version 3.0.6 (Janvier 2025)
|
||||
|
||||
#### 1. Correction des rôles administrateurs
|
||||
- **Avant :** Les administrateurs d'amicale devaient avoir `fk_role > 2`
|
||||
- **Après :** Les administrateurs d'amicale ont `fk_role > 1` (donc rôle 2 et plus)
|
||||
- **Impact :** Les champs `chk_stripe`, `chk_mdp_manuel`, `chk_username_manuel` sont maintenant modifiables par les admins d'amicale (rôle 2)
|
||||
|
||||
#### 2. Envoi systématique des deux emails lors de la création d'utilisateur
|
||||
- **Avant :** Le 2ème email (mot de passe) n'était envoyé que si le mot de passe était généré automatiquement
|
||||
- **Après :** Les deux emails sont toujours envoyés lors de la création d'un membre
|
||||
- Email 1 : Identifiant (username)
|
||||
- Email 2 : Mot de passe (1 seconde après)
|
||||
- **Raison :** Le nouveau membre a toujours besoin des deux informations pour se connecter
|
||||
|
||||
#### 3. Ajout des champs manquants dans la réponse du login
|
||||
- **Champs ajoutés dans la requête SQL :**
|
||||
- `fk_titre`
|
||||
- `date_naissance`
|
||||
- `date_embauche`
|
||||
- `encrypted_phone`
|
||||
- `encrypted_mobile`
|
||||
- **Impact :** Ces données sont maintenant correctement retournées dans l'objet `user` lors du login
|
||||
|
||||
#### 4. Système de gestion des logos d'entité
|
||||
- **Nouvelle fonctionnalité :** Upload et gestion des logos pour les amicales
|
||||
- **Routes ajoutées :**
|
||||
- `POST /api/entites/{id}/logo` : Upload d'un nouveau logo
|
||||
- `GET /api/entites/{id}/logo` : Récupération du logo
|
||||
- **Caractéristiques :**
|
||||
- Réservé aux administrateurs d'amicale (fk_role == 2)
|
||||
- Un seul logo actif par entité
|
||||
- Redimensionnement automatique (250x250px max)
|
||||
- Format base64 dans les réponses JSON (compatible Flutter)
|
||||
- Logo inclus automatiquement dans la réponse du login
|
||||
|
||||
#### 5. Amélioration de l'intégration Flutter
|
||||
- **Format d'envoi des images :** Base64 data URL pour compatibilité multiplateforme
|
||||
- **Structure de réponse enrichie :** Le logo est inclus dans l'objet `amicale` lors du login
|
||||
- **Optimisation :** Pas de requête HTTP supplémentaire nécessaire pour afficher le logo
|
||||
|
||||
@@ -220,7 +220,8 @@ CREATE TABLE `entites` (
|
||||
`encrypted_iban` varchar(255) DEFAULT '',
|
||||
`encrypted_bic` varchar(128) DEFAULT '',
|
||||
`chk_demo` tinyint(1) unsigned DEFAULT 1,
|
||||
`chk_mdp_manuel` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT 'Gestion des mots de passe manuelle O/N',
|
||||
`chk_mdp_manuel` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Gestion des mots de passe manuelle (1) ou automatique (0)',
|
||||
`chk_username_manuel` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Gestion des usernames manuelle (1) ou automatique (0)',
|
||||
`chk_copie_mail_recu` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`chk_accept_sms` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
|
||||
25
api/scripts/sql/add_chk_username_manuel.sql
Normal file
25
api/scripts/sql/add_chk_username_manuel.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- Script de migration pour ajouter le champ chk_username_manuel et modifier chk_mdp_manuel
|
||||
-- Date: 2025-08-07
|
||||
-- Description: Permet aux administrateurs d'entité de choisir entre saisie manuelle ou génération automatique des usernames et mots de passe
|
||||
|
||||
-- Modification du champ chk_mdp_manuel pour mettre la valeur par défaut à 0 (génération automatique)
|
||||
ALTER TABLE `entites`
|
||||
MODIFY COLUMN `chk_mdp_manuel` tinyint(1) unsigned NOT NULL DEFAULT 0
|
||||
COMMENT 'Gestion des mots de passe manuelle (1) ou automatique (0)';
|
||||
|
||||
-- Ajout du champ chk_username_manuel dans la table entites
|
||||
ALTER TABLE `entites`
|
||||
ADD COLUMN `chk_username_manuel` tinyint(1) unsigned NOT NULL DEFAULT 0
|
||||
COMMENT 'Gestion des usernames manuelle (1) ou automatique (0)'
|
||||
AFTER `chk_mdp_manuel`;
|
||||
|
||||
-- Par défaut, on met à 0 (génération automatique) pour toutes les entités existantes
|
||||
-- Cela permet de garder le comportement actuel par défaut
|
||||
UPDATE `entites` SET `chk_username_manuel` = 0 WHERE `chk_username_manuel` IS NULL;
|
||||
|
||||
-- Ajout d'un index sur encrypted_user_name pour améliorer les performances de vérification d'unicité
|
||||
ALTER TABLE `users`
|
||||
ADD INDEX `idx_encrypted_user_name` (`encrypted_user_name`);
|
||||
|
||||
-- Message de confirmation
|
||||
SELECT CONCAT('Migration effectuée : chk_username_manuel ajouté à la table entites') AS 'Status';
|
||||
@@ -415,7 +415,7 @@ class EntiteController {
|
||||
}
|
||||
|
||||
$userRole = (int)$user['fk_role'];
|
||||
$isAdmin = $userRole > 2;
|
||||
$isAdmin = $userRole > 1;
|
||||
|
||||
// Récupérer les données de la requête
|
||||
$data = Request::getJson();
|
||||
@@ -577,6 +577,16 @@ class EntiteController {
|
||||
$updateFields[] = 'chk_stripe = ?';
|
||||
$params[] = $data['chk_stripe'] ? 1 : 0;
|
||||
}
|
||||
|
||||
if (isset($data['chk_mdp_manuel'])) {
|
||||
$updateFields[] = 'chk_mdp_manuel = ?';
|
||||
$params[] = $data['chk_mdp_manuel'] ? 1 : 0;
|
||||
}
|
||||
|
||||
if (isset($data['chk_username_manuel'])) {
|
||||
$updateFields[] = 'chk_username_manuel = ?';
|
||||
$params[] = $data['chk_username_manuel'] ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Si aucun champ à mettre à jour, retourner une erreur
|
||||
@@ -631,4 +641,341 @@ class EntiteController {
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload et traite le logo d'une entité
|
||||
* Réservé aux administrateurs d'amicale (fk_role == 2)
|
||||
*
|
||||
* @param string $id ID de l'entité
|
||||
* @return void
|
||||
*/
|
||||
public function uploadLogo(string $id): void {
|
||||
try {
|
||||
// Vérifier l'authentification
|
||||
$userId = Session::getUserId();
|
||||
if (!$userId) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Vous devez être connecté pour effectuer cette action'
|
||||
], 401);
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupérer les infos de l'utilisateur
|
||||
$stmt = $this->db->prepare('SELECT fk_role, fk_entite FROM users WHERE id = ?');
|
||||
$stmt->execute([$userId]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$user) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Utilisateur non trouvé'
|
||||
], 404);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier que l'utilisateur est admin d'amicale (fk_role == 2)
|
||||
if ((int)$user['fk_role'] !== 2) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Seuls les administrateurs d\'amicale peuvent uploader un logo'
|
||||
], 403);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier que l'entité correspond à celle de l'utilisateur
|
||||
$entiteId = (int)$id;
|
||||
if ($entiteId !== (int)$user['fk_entite']) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Vous ne pouvez modifier que le logo de votre propre amicale'
|
||||
], 403);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier qu'un fichier a été envoyé
|
||||
if (!isset($_FILES['logo']) || $_FILES['logo']['error'] !== UPLOAD_ERR_OK) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Aucun fichier reçu ou erreur lors de l\'upload'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$uploadedFile = $_FILES['logo'];
|
||||
|
||||
// Vérifier le type MIME
|
||||
$allowedMimes = ['image/jpeg', 'image/jpg', 'image/png'];
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $uploadedFile['tmp_name']);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (!in_array($mimeType, $allowedMimes)) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Format de fichier non autorisé. Seuls PNG, JPG et JPEG sont acceptés'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
// Déterminer l'extension
|
||||
$extension = match($mimeType) {
|
||||
'image/jpeg', 'image/jpg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
default => 'jpg'
|
||||
};
|
||||
|
||||
// Créer le dossier de destination
|
||||
require_once __DIR__ . '/../Services/FileService.php';
|
||||
$fileService = new \FileService();
|
||||
$uploadPath = "/entites/{$entiteId}/logo";
|
||||
$fullPath = $fileService->createDirectory($entiteId, $uploadPath);
|
||||
|
||||
// Nom du fichier final
|
||||
$fileName = "logo_{$entiteId}_" . time() . ".{$extension}";
|
||||
$filePath = $fullPath . '/' . $fileName;
|
||||
|
||||
// Charger l'image avec GD
|
||||
$sourceImage = match($mimeType) {
|
||||
'image/jpeg', 'image/jpg' => imagecreatefromjpeg($uploadedFile['tmp_name']),
|
||||
'image/png' => imagecreatefrompng($uploadedFile['tmp_name']),
|
||||
default => false
|
||||
};
|
||||
|
||||
if (!$sourceImage) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Impossible de traiter l\'image'
|
||||
], 500);
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtenir les dimensions originales
|
||||
$originalWidth = imagesx($sourceImage);
|
||||
$originalHeight = imagesy($sourceImage);
|
||||
|
||||
// Calculer les nouvelles dimensions (max 250px) en gardant le ratio
|
||||
$maxSize = 250;
|
||||
$ratio = min($maxSize / $originalWidth, $maxSize / $originalHeight, 1);
|
||||
$newWidth = (int)($originalWidth * $ratio);
|
||||
$newHeight = (int)($originalHeight * $ratio);
|
||||
|
||||
// Créer l'image redimensionnée
|
||||
$resizedImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
|
||||
// Préserver la transparence pour les PNG
|
||||
if ($mimeType === 'image/png') {
|
||||
imagealphablending($resizedImage, false);
|
||||
imagesavealpha($resizedImage, true);
|
||||
$transparent = imagecolorallocatealpha($resizedImage, 255, 255, 255, 127);
|
||||
imagefilledrectangle($resizedImage, 0, 0, $newWidth, $newHeight, $transparent);
|
||||
}
|
||||
|
||||
// Redimensionner
|
||||
imagecopyresampled(
|
||||
$resizedImage, $sourceImage,
|
||||
0, 0, 0, 0,
|
||||
$newWidth, $newHeight,
|
||||
$originalWidth, $originalHeight
|
||||
);
|
||||
|
||||
// Sauvegarder l'image (72 DPI est la résolution standard web)
|
||||
$saved = match($mimeType) {
|
||||
'image/jpeg', 'image/jpg' => imagejpeg($resizedImage, $filePath, 85),
|
||||
'image/png' => imagepng($resizedImage, $filePath, 8),
|
||||
default => false
|
||||
};
|
||||
|
||||
// Libérer la mémoire
|
||||
imagedestroy($sourceImage);
|
||||
imagedestroy($resizedImage);
|
||||
|
||||
if (!$saved) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Impossible de sauvegarder l\'image'
|
||||
], 500);
|
||||
return;
|
||||
}
|
||||
|
||||
// Appliquer les permissions
|
||||
$fileService->setFilePermissions($filePath);
|
||||
|
||||
// Supprimer l'ancien logo s'il existe
|
||||
$stmt = $this->db->prepare('
|
||||
SELECT id, file_path FROM medias
|
||||
WHERE support = ? AND support_id = ? AND file_category = ?
|
||||
');
|
||||
$stmt->execute(['entite', $entiteId, 'logo']);
|
||||
$oldLogo = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($oldLogo && !empty($oldLogo['file_path']) && file_exists($oldLogo['file_path'])) {
|
||||
unlink($oldLogo['file_path']);
|
||||
// Supprimer l'entrée de la base
|
||||
$stmt = $this->db->prepare('DELETE FROM medias WHERE id = ?');
|
||||
$stmt->execute([$oldLogo['id']]);
|
||||
}
|
||||
|
||||
// Enregistrer dans la table medias
|
||||
$stmt = $this->db->prepare('
|
||||
INSERT INTO medias (
|
||||
support, support_id, fichier, file_type, file_category,
|
||||
file_size, mime_type, original_name, fk_entite,
|
||||
file_path, original_width, original_height,
|
||||
processed_width, processed_height, is_processed,
|
||||
description, created_at, fk_user_creat
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
'entite', // support
|
||||
$entiteId, // support_id
|
||||
$fileName, // fichier
|
||||
$extension, // file_type
|
||||
'logo', // file_category
|
||||
filesize($filePath), // file_size
|
||||
$mimeType, // mime_type
|
||||
$uploadedFile['name'], // original_name
|
||||
$entiteId, // fk_entite
|
||||
$filePath, // file_path
|
||||
$originalWidth, // original_width
|
||||
$originalHeight, // original_height
|
||||
$newWidth, // processed_width
|
||||
$newHeight, // processed_height
|
||||
1, // is_processed
|
||||
'Logo de l\'entité', // description
|
||||
$userId // fk_user_creat
|
||||
]);
|
||||
|
||||
$mediaId = $this->db->lastInsertId();
|
||||
|
||||
LogService::log('Upload de logo réussi', [
|
||||
'level' => 'info',
|
||||
'userId' => $userId,
|
||||
'entiteId' => $entiteId,
|
||||
'mediaId' => $mediaId,
|
||||
'fileName' => $fileName,
|
||||
'originalSize' => "{$originalWidth}x{$originalHeight}",
|
||||
'newSize' => "{$newWidth}x{$newHeight}"
|
||||
]);
|
||||
|
||||
Response::json([
|
||||
'status' => 'success',
|
||||
'message' => 'Logo uploadé avec succès',
|
||||
'media_id' => $mediaId,
|
||||
'file_name' => $fileName,
|
||||
'file_path' => $uploadPath . '/' . $fileName,
|
||||
'dimensions' => [
|
||||
'width' => $newWidth,
|
||||
'height' => $newHeight
|
||||
]
|
||||
], 200);
|
||||
|
||||
} catch (Exception $e) {
|
||||
LogService::log('Erreur lors de l\'upload du logo', [
|
||||
'level' => 'error',
|
||||
'error' => $e->getMessage(),
|
||||
'entiteId' => $id
|
||||
]);
|
||||
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Erreur lors de l\'upload du logo'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le logo d'une entité
|
||||
*
|
||||
* @param string $id ID de l'entité
|
||||
* @return void
|
||||
*/
|
||||
public function getLogo(string $id): void {
|
||||
try {
|
||||
// Vérifier l'authentification
|
||||
$userId = Session::getUserId();
|
||||
if (!$userId) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Vous devez être connecté pour effectuer cette action'
|
||||
], 401);
|
||||
return;
|
||||
}
|
||||
|
||||
$entiteId = (int)$id;
|
||||
|
||||
// Récupérer le logo depuis la base
|
||||
$stmt = $this->db->prepare('
|
||||
SELECT id, fichier, file_path, file_type, mime_type,
|
||||
processed_width, processed_height, file_size
|
||||
FROM medias
|
||||
WHERE support = ? AND support_id = ? AND file_category = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
');
|
||||
$stmt->execute(['entite', $entiteId, 'logo']);
|
||||
$logo = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$logo) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Aucun logo trouvé pour cette entité'
|
||||
], 404);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file_exists($logo['file_path'])) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Fichier logo introuvable'
|
||||
], 404);
|
||||
return;
|
||||
}
|
||||
|
||||
// Option 1 : Retourner l'image directement (pour une URL séparée)
|
||||
// header('Content-Type: ' . $logo['mime_type']);
|
||||
// header('Content-Length: ' . $logo['file_size']);
|
||||
// header('Cache-Control: public, max-age=86400'); // Cache 24h
|
||||
// readfile($logo['file_path']);
|
||||
|
||||
// Option 2 : Retourner en base64 dans JSON (recommandé pour Flutter)
|
||||
$imageData = file_get_contents($logo['file_path']);
|
||||
if ($imageData === false) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Impossible de lire le fichier logo'
|
||||
], 500);
|
||||
return;
|
||||
}
|
||||
|
||||
$base64 = base64_encode($imageData);
|
||||
$dataUrl = 'data:' . $logo['mime_type'] . ';base64,' . $base64;
|
||||
|
||||
Response::json([
|
||||
'status' => 'success',
|
||||
'logo' => [
|
||||
'id' => $logo['id'],
|
||||
'data_url' => $dataUrl,
|
||||
'file_name' => $logo['fichier'],
|
||||
'mime_type' => $logo['mime_type'],
|
||||
'width' => $logo['processed_width'],
|
||||
'height' => $logo['processed_height'],
|
||||
'size' => $logo['file_size']
|
||||
]
|
||||
], 200);
|
||||
|
||||
} catch (Exception $e) {
|
||||
LogService::log('Erreur lors de la récupération du logo', [
|
||||
'level' => 'error',
|
||||
'error' => $e->getMessage(),
|
||||
'entiteId' => $id
|
||||
]);
|
||||
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Erreur lors de la récupération du logo'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ class LoginController {
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT
|
||||
u.id, u.encrypted_email, u.encrypted_user_name, u.encrypted_name, u.user_pass_hash,
|
||||
u.first_name, u.fk_role, u.fk_entite, u.chk_active, u.sect_name,
|
||||
u.first_name, u.fk_role, u.fk_entite, u.fk_titre, u.chk_active, u.sect_name,
|
||||
u.date_naissance, u.date_embauche, u.encrypted_phone, u.encrypted_mobile,
|
||||
e.id AS entite_id, e.encrypted_name AS entite_encrypted_name,
|
||||
e.adresse1, e.code_postal, e.ville, e.gps_lat, e.gps_lng, e.chk_active AS entite_chk_active
|
||||
FROM users u
|
||||
@@ -432,7 +433,7 @@ class LoginController {
|
||||
}
|
||||
}
|
||||
|
||||
// Section "clients" supprimée car redondante avec le nouveau groupe "amicales"
|
||||
// 5. Section clients gérée plus bas pour les super-administrateurs
|
||||
|
||||
// 6. Récupérer les membres (users de l'entité du user) si nécessaire
|
||||
if ($interface === 'admin' && $user['fk_role'] == 2 && !empty($user['fk_entite'])) {
|
||||
@@ -518,7 +519,8 @@ class LoginController {
|
||||
'SELECT e.id, e.encrypted_name as name, e.adresse1, e.adresse2, e.code_postal, e.ville,
|
||||
e.fk_region, r.libelle AS lib_region, e.fk_type, e.encrypted_phone as phone, e.encrypted_mobile as mobile,
|
||||
e.encrypted_email as email, e.gps_lat, e.gps_lng,
|
||||
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
|
||||
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_mdp_manuel, e.chk_username_manuel,
|
||||
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
|
||||
FROM entites e
|
||||
LEFT JOIN x_regions r ON e.fk_region = r.id
|
||||
WHERE e.id = ? AND e.chk_active = 1'
|
||||
@@ -531,7 +533,8 @@ class LoginController {
|
||||
'SELECT e.id, e.encrypted_name as name, e.adresse1, e.adresse2, e.code_postal, e.ville,
|
||||
e.fk_region, r.libelle AS lib_region, e.fk_type, e.encrypted_phone as phone, e.encrypted_mobile as mobile,
|
||||
e.encrypted_email as email, e.gps_lat, e.gps_lng,
|
||||
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
|
||||
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_mdp_manuel, e.chk_username_manuel,
|
||||
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
|
||||
FROM entites e
|
||||
LEFT JOIN x_regions r ON e.fk_region = r.id
|
||||
WHERE e.id != 1 AND e.chk_active = 1'
|
||||
@@ -583,7 +586,8 @@ class LoginController {
|
||||
'SELECT e.id, e.encrypted_name as name, e.adresse1, e.adresse2, e.code_postal, e.ville,
|
||||
e.fk_region, r.libelle AS lib_region, e.fk_type, e.encrypted_phone as phone, e.encrypted_mobile as mobile,
|
||||
e.encrypted_email as email, e.gps_lat, e.gps_lng,
|
||||
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
|
||||
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_mdp_manuel, e.chk_username_manuel,
|
||||
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
|
||||
FROM entites e
|
||||
LEFT JOIN x_regions r ON e.fk_region = r.id
|
||||
WHERE e.fk_type = 1 AND e.chk_active = 1'
|
||||
@@ -636,12 +640,58 @@ class LoginController {
|
||||
|
||||
// Ajout des amicales à la racine de la réponse si disponibles
|
||||
if (!empty($amicalesData)) {
|
||||
// Récupérer le logo de l'entité de l'utilisateur si elle existe
|
||||
$logoData = null;
|
||||
if (!empty($user['fk_entite'])) {
|
||||
$logoStmt = $this->db->prepare('
|
||||
SELECT id, fichier, file_path, file_type, mime_type, processed_width, processed_height
|
||||
FROM medias
|
||||
WHERE support = ? AND support_id = ? AND file_category = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
');
|
||||
$logoStmt->execute(['entite', $user['fk_entite'], 'logo']);
|
||||
$logo = $logoStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($logo && file_exists($logo['file_path'])) {
|
||||
// Lire le fichier et l'encoder en base64
|
||||
$imageData = file_get_contents($logo['file_path']);
|
||||
if ($imageData !== false) {
|
||||
$base64 = base64_encode($imageData);
|
||||
// Format data URL pour usage direct dans Flutter
|
||||
$dataUrl = 'data:' . $logo['mime_type'] . ';base64,' . $base64;
|
||||
|
||||
$logoData = [
|
||||
'id' => $logo['id'],
|
||||
'data_url' => $dataUrl, // Image encodée en base64
|
||||
'file_name' => $logo['fichier'],
|
||||
'mime_type' => $logo['mime_type'],
|
||||
'width' => $logo['processed_width'],
|
||||
'height' => $logo['processed_height']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si c'est un tableau avec un seul élément, on envoie directement l'objet
|
||||
// pour que le client reçoive un objet et non un tableau avec un seul objet
|
||||
if (count($amicalesData) === 1) {
|
||||
$response['amicale'] = $amicalesData[0];
|
||||
// Ajouter le logo à l'amicale si disponible
|
||||
if ($logoData !== null) {
|
||||
$response['amicale']['logo'] = $logoData;
|
||||
}
|
||||
} else {
|
||||
$response['amicale'] = $amicalesData;
|
||||
// Pour plusieurs amicales, ajouter le logo à celle de l'utilisateur
|
||||
if ($logoData !== null && !empty($user['fk_entite'])) {
|
||||
foreach ($response['amicale'] as &$amicale) {
|
||||
if ($amicale['id'] == $user['fk_entite']) {
|
||||
$amicale['logo'] = $logoData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,7 +723,7 @@ class LoginController {
|
||||
$response['users_sectors'] = $usersSectorsData;
|
||||
}
|
||||
|
||||
// Section "clients" supprimée car redondante avec le nouveau groupe "amicales"
|
||||
// 5. Section clients gérée plus bas pour les super-administrateurs
|
||||
|
||||
// 9. Récupérer les régions selon le rôle de l'utilisateur
|
||||
$regionsData = [];
|
||||
|
||||
@@ -227,6 +227,26 @@ class UserController {
|
||||
$role = isset($data['role']) ? (int)$data['role'] : 1;
|
||||
$entiteId = isset($data['fk_entite']) ? (int)$data['fk_entite'] : 1;
|
||||
|
||||
// Récupérer les paramètres de gestion de l'entité
|
||||
$entiteStmt = $this->db->prepare('
|
||||
SELECT chk_mdp_manuel, chk_username_manuel, code_postal, ville
|
||||
FROM entites
|
||||
WHERE id = ?
|
||||
');
|
||||
$entiteStmt->execute([$entiteId]);
|
||||
$entiteConfig = $entiteStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$entiteConfig) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Entité non trouvée'
|
||||
], 404);
|
||||
return;
|
||||
}
|
||||
|
||||
$chkMdpManuel = (int)$entiteConfig['chk_mdp_manuel'];
|
||||
$chkUsernameManuel = (int)$entiteConfig['chk_username_manuel'];
|
||||
|
||||
// Vérification des longueurs d'entrée
|
||||
if (strlen($email) > 75 || strlen($name) > 50) {
|
||||
Response::json([
|
||||
@@ -260,9 +280,83 @@ class UserController {
|
||||
return;
|
||||
}
|
||||
|
||||
// Génération du mot de passe
|
||||
$password = ApiService::generateSecurePassword();
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
// Gestion du USERNAME selon chk_username_manuel
|
||||
$encryptedUsername = '';
|
||||
if ($chkUsernameManuel === 1) {
|
||||
// Username manuel obligatoire
|
||||
if (!isset($data['username']) || empty(trim($data['username']))) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Le nom d\'utilisateur est requis pour cette entité'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$username = trim(strtolower($data['username']));
|
||||
|
||||
// Validation du format du username
|
||||
if (!preg_match('/^[a-z][a-z0-9._-]{9,29}$/', $username)) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Format du nom d\'utilisateur invalide (10-30 caractères, commence par une lettre, caractères autorisés: a-z, 0-9, ., -, _)'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$encryptedUsername = ApiService::encryptSearchableData($username);
|
||||
|
||||
// Vérification de l'unicité du username
|
||||
$checkUsernameStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ?');
|
||||
$checkUsernameStmt->execute([$encryptedUsername]);
|
||||
if ($checkUsernameStmt->fetch()) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Ce nom d\'utilisateur est déjà utilisé dans GeoSector'
|
||||
], 409);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Génération automatique du username
|
||||
$username = ApiService::generateUserName(
|
||||
$this->db,
|
||||
$name,
|
||||
$entiteConfig['code_postal'] ?? '00000',
|
||||
$entiteConfig['ville'] ?? 'ville',
|
||||
10
|
||||
);
|
||||
$encryptedUsername = ApiService::encryptSearchableData($username);
|
||||
}
|
||||
|
||||
// Gestion du MOT DE PASSE selon chk_mdp_manuel
|
||||
$password = '';
|
||||
$passwordHash = '';
|
||||
if ($chkMdpManuel === 1) {
|
||||
// Mot de passe manuel obligatoire
|
||||
if (!isset($data['password']) || empty($data['password'])) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Le mot de passe est requis pour cette entité'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$password = $data['password'];
|
||||
|
||||
// Validation du mot de passe (minimum 8 caractères)
|
||||
if (strlen($password) < 8) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Le mot de passe doit contenir au moins 8 caractères'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
} else {
|
||||
// Génération automatique du mot de passe
|
||||
$password = ApiService::generateSecurePassword();
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
// Préparation des champs optionnels
|
||||
$phone = isset($data['phone']) ? ApiService::encryptData(trim($data['phone'])) : null;
|
||||
@@ -276,13 +370,13 @@ class UserController {
|
||||
// Insertion en base de données
|
||||
$stmt = $this->db->prepare('
|
||||
INSERT INTO users (
|
||||
encrypted_email, user_pass_hash, encrypted_name, first_name,
|
||||
encrypted_email, encrypted_user_name, user_pass_hash, encrypted_name, first_name,
|
||||
sect_name, encrypted_phone, encrypted_mobile, fk_role,
|
||||
fk_entite, chk_alert_email, chk_suivi,
|
||||
date_naissance, date_embauche,
|
||||
created_at, fk_user_creat, chk_active
|
||||
) VALUES (
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?,
|
||||
?, ?,
|
||||
@@ -291,6 +385,7 @@ class UserController {
|
||||
');
|
||||
$stmt->execute([
|
||||
$encryptedEmail,
|
||||
$encryptedUsername,
|
||||
$passwordHash,
|
||||
$encryptedName,
|
||||
$firstName,
|
||||
@@ -307,21 +402,54 @@ class UserController {
|
||||
]);
|
||||
$userId = $this->db->lastInsertId();
|
||||
|
||||
// Envoi de l'email avec les identifiants
|
||||
ApiService::sendEmail($email, $name, 'welcome', ['password' => $password]);
|
||||
// Envoi des emails séparés pour plus de sécurité
|
||||
|
||||
// 1er email : TOUJOURS envoyer l'identifiant (username)
|
||||
$usernameEmailData = [
|
||||
'email' => $email,
|
||||
'username' => $username,
|
||||
'name' => $name
|
||||
];
|
||||
ApiService::sendEmail($email, $name, 'welcome_username', $usernameEmailData);
|
||||
|
||||
// 2ème email : Envoyer le mot de passe (toujours, qu'il soit manuel ou généré)
|
||||
// Attendre un peu entre les deux emails pour éviter qu'ils arrivent dans le mauvais ordre
|
||||
sleep(1);
|
||||
|
||||
$passwordEmailData = [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name
|
||||
];
|
||||
ApiService::sendEmail($email, $name, 'welcome_password', $passwordEmailData);
|
||||
|
||||
LogService::log('Utilisateur GeoSector créé', [
|
||||
'level' => 'info',
|
||||
'createdBy' => $currentUserId,
|
||||
'newUserId' => $userId,
|
||||
'email' => $email
|
||||
'email' => $email,
|
||||
'username' => $username,
|
||||
'usernameManual' => $chkUsernameManuel === 1 ? 'oui' : 'non',
|
||||
'passwordManual' => $chkMdpManuel === 1 ? 'oui' : 'non',
|
||||
'emailsSent' => '2 emails (username + password)'
|
||||
]);
|
||||
|
||||
Response::json([
|
||||
// Préparer la réponse avec les informations de connexion si générées automatiquement
|
||||
$responseData = [
|
||||
'status' => 'success',
|
||||
'message' => 'Utilisateur créé avec succès',
|
||||
'id' => $userId
|
||||
], 201);
|
||||
];
|
||||
|
||||
// Ajouter le username dans la réponse (toujours, car nécessaire pour la connexion)
|
||||
$responseData['username'] = $username;
|
||||
|
||||
// Ajouter le mot de passe seulement si généré automatiquement
|
||||
if ($chkMdpManuel === 0) {
|
||||
$responseData['password'] = $password;
|
||||
}
|
||||
|
||||
Response::json($responseData, 201);
|
||||
} catch (PDOException $e) {
|
||||
LogService::log('Erreur lors de la création d\'un utilisateur GeoSector', [
|
||||
'level' => 'error',
|
||||
@@ -756,6 +884,106 @@ class UserController {
|
||||
}
|
||||
}
|
||||
|
||||
public function checkUsername(): void {
|
||||
Session::requireAuth();
|
||||
|
||||
try {
|
||||
$data = Request::getJson();
|
||||
|
||||
// Validation de la présence du username
|
||||
if (!isset($data['username']) || empty(trim($data['username']))) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Username requis pour la vérification'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$username = trim(strtolower($data['username']));
|
||||
|
||||
// Validation du format du username
|
||||
if (!preg_match('/^[a-z][a-z0-9._-]{9,29}$/', $username)) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Format invalide : 10-30 caractères, commence par une lettre, caractères autorisés: a-z, 0-9, ., -, _',
|
||||
'available' => false
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
// Chiffrement du username pour la recherche
|
||||
$encryptedUsername = ApiService::encryptSearchableData($username);
|
||||
|
||||
// Vérification de l'existence dans la base
|
||||
$stmt = $this->db->prepare('
|
||||
SELECT id, encrypted_name, fk_entite
|
||||
FROM users
|
||||
WHERE encrypted_user_name = ?
|
||||
LIMIT 1
|
||||
');
|
||||
$stmt->execute([$encryptedUsername]);
|
||||
$existingUser = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($existingUser) {
|
||||
// Username déjà pris - générer des suggestions
|
||||
$baseName = substr($username, 0, -2); // Enlever les 2 derniers caractères
|
||||
$suggestions = [];
|
||||
|
||||
// Génération de 3 suggestions
|
||||
$suggestions[] = $username . '_' . rand(10, 99);
|
||||
$suggestions[] = $baseName . rand(100, 999);
|
||||
|
||||
// Suggestion avec l'année courante
|
||||
$year = date('y');
|
||||
$suggestions[] = $username . $year;
|
||||
|
||||
// Vérifier que les suggestions sont aussi disponibles
|
||||
$availableSuggestions = [];
|
||||
foreach ($suggestions as $suggestion) {
|
||||
$encryptedSuggestion = ApiService::encryptSearchableData($suggestion);
|
||||
$checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ?');
|
||||
$checkStmt->execute([$encryptedSuggestion]);
|
||||
if (!$checkStmt->fetch()) {
|
||||
$availableSuggestions[] = $suggestion;
|
||||
}
|
||||
}
|
||||
|
||||
// Si aucune suggestion n'est disponible, en générer d'autres
|
||||
if (empty($availableSuggestions)) {
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$randomSuffix = rand(1000, 9999);
|
||||
$availableSuggestions[] = $baseName . $randomSuffix;
|
||||
}
|
||||
}
|
||||
|
||||
Response::json([
|
||||
'status' => 'success',
|
||||
'available' => false,
|
||||
'message' => 'Ce nom d\'utilisateur est déjà utilisé',
|
||||
'suggestions' => array_slice($availableSuggestions, 0, 3)
|
||||
]);
|
||||
} else {
|
||||
// Username disponible
|
||||
Response::json([
|
||||
'status' => 'success',
|
||||
'available' => true,
|
||||
'message' => 'Nom d\'utilisateur disponible',
|
||||
'username' => $username
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
LogService::log('Erreur lors de la vérification du username', [
|
||||
'level' => 'error',
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Erreur serveur lors de la vérification'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes auxiliaires
|
||||
private function validateUpdateData(array $data): ?string {
|
||||
// Validation de l'email
|
||||
|
||||
@@ -38,6 +38,7 @@ class Router {
|
||||
$this->put('users/:id', ['UserController', 'updateUser']);
|
||||
$this->delete('users/:id', ['UserController', 'deleteUser']);
|
||||
$this->post('users/:id/reset-password', ['UserController', 'resetPassword']);
|
||||
$this->post('users/check-username', ['UserController', 'checkUsername']);
|
||||
$this->post('logout', ['LoginController', 'logout']);
|
||||
|
||||
// Routes entités
|
||||
@@ -45,6 +46,8 @@ class Router {
|
||||
$this->get('entites/:id', ['EntiteController', 'getEntiteById']);
|
||||
$this->get('entites/postal/:code', ['EntiteController', 'getEntiteByPostalCode']);
|
||||
$this->put('entites/:id', ['EntiteController', 'updateEntite']);
|
||||
$this->post('entites/:id/logo', ['EntiteController', 'uploadLogo']);
|
||||
$this->get('entites/:id/logo', ['EntiteController', 'getLogo']);
|
||||
|
||||
// Routes opérations
|
||||
$this->get('operations', ['OperationController', 'getOperations']);
|
||||
|
||||
@@ -53,6 +53,16 @@ class ApiService {
|
||||
$mail->Body = EmailTemplates::getWelcomeTemplate($name, $data['username'] ?? '', $data['password']);
|
||||
break;
|
||||
|
||||
case 'welcome_username':
|
||||
$mail->Subject = 'GEOSECTOR - Votre identifiant de connexion';
|
||||
$mail->Body = EmailTemplates::getWelcomeUsernameTemplate($name, $data['username'] ?? '');
|
||||
break;
|
||||
|
||||
case 'welcome_password':
|
||||
$mail->Subject = 'GEOSECTOR - Votre mot de passe';
|
||||
$mail->Body = EmailTemplates::getWelcomePasswordTemplate($name, $data['password'] ?? '');
|
||||
break;
|
||||
|
||||
case 'lostpwd':
|
||||
$mail->Subject = 'Réinitialisation de votre mot de passe GEOSECTOR';
|
||||
$mail->Body = EmailTemplates::getLostPasswordTemplate($name, $data['username'] ?? '', $data['password']);
|
||||
|
||||
@@ -17,6 +17,59 @@ class EmailTemplates {
|
||||
L'équipe GeoSector";
|
||||
}
|
||||
|
||||
/**
|
||||
* Template d'email de bienvenue - Identifiant uniquement
|
||||
*/
|
||||
public static function getWelcomeUsernameTemplate(string $name, string $username): string {
|
||||
return "
|
||||
Bonjour $name,<br><br>
|
||||
Votre compte a été créé avec succès sur <b>GeoSector</b>.<br><br>
|
||||
Voici votre identifiant de connexion :<br>
|
||||
<div style='background:#f5f5f5; padding:15px; margin:20px 0; border-left:4px solid #007bff;'>
|
||||
<b style='font-size:16px;'>Identifiant :</b> <span style='font-size:18px; color:#333;'>$username</span>
|
||||
</div>
|
||||
<p style='color:#666; font-size:14px;'>
|
||||
<b>Important :</b> Conservez précieusement cet identifiant, vous en aurez besoin pour vous connecter.
|
||||
</p>
|
||||
<p>
|
||||
Votre mot de passe vous sera communiqué dans un email séparé pour des raisons de sécurité.
|
||||
</p>
|
||||
<p>
|
||||
Une fois que vous aurez reçu votre mot de passe, vous pourrez vous connecter sur
|
||||
<a href=\"https://app.geosector.fr\" style='color:#007bff;'>app.geosector.fr</a>
|
||||
</p>
|
||||
<br>
|
||||
À très bientôt,<br>
|
||||
L'équipe GeoSector";
|
||||
}
|
||||
|
||||
/**
|
||||
* Template d'email de bienvenue - Mot de passe uniquement
|
||||
*/
|
||||
public static function getWelcomePasswordTemplate(string $name, string $password): string {
|
||||
return "
|
||||
Bonjour $name,<br><br>
|
||||
Suite à la création de votre compte <b>GeoSector</b>, voici votre mot de passe :<br><br>
|
||||
<div style='background:#f5f5f5; padding:15px; margin:20px 0; border-left:4px solid #28a745;'>
|
||||
<b style='font-size:16px;'>Mot de passe :</b> <span style='font-family:monospace; font-size:18px; color:#333;'>$password</span>
|
||||
</div>
|
||||
<p style='color:#d73502; font-size:14px;'>
|
||||
<b>⚠ Sécurité :</b> Pour garantir la sécurité de votre compte, nous vous recommandons
|
||||
de conserver ce mot de passe en lieu sûr et de ne jamais le partager.
|
||||
</p>
|
||||
<p>
|
||||
Vous pouvez maintenant vous connecter avec votre identifiant (reçu dans un email précédent)
|
||||
et ce mot de passe sur <a href=\"https://app.geosector.fr\" style='color:#007bff;'>app.geosector.fr</a>
|
||||
</p>
|
||||
<p style='background:#fff3cd; padding:10px; border-radius:5px; margin-top:20px;'>
|
||||
<b>Rappel :</b> Ne communiquez jamais votre mot de passe à un tiers. L'équipe GeoSector
|
||||
ne vous demandera jamais votre mot de passe par email ou téléphone.
|
||||
</p>
|
||||
<br>
|
||||
À très bientôt,<br>
|
||||
L'équipe GeoSector";
|
||||
}
|
||||
|
||||
/**
|
||||
* Template d'email pour mot de passe perdu
|
||||
*/
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -35,13 +35,16 @@ class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
|
||||
chkStripe: fields[19] as bool,
|
||||
createdAt: fields[20] as DateTime?,
|
||||
updatedAt: fields[21] as DateTime?,
|
||||
chkMdpManuel: fields[22] as bool,
|
||||
chkUsernameManuel: fields[23] as bool,
|
||||
logoBase64: fields[24] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, AmicaleModel obj) {
|
||||
writer
|
||||
..writeByte(22)
|
||||
..writeByte(25)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
@@ -85,7 +88,13 @@ class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
|
||||
..writeByte(20)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(21)
|
||||
..write(obj.updatedAt);
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(22)
|
||||
..write(obj.chkMdpManuel)
|
||||
..writeByte(23)
|
||||
..write(obj.chkUsernameManuel)
|
||||
..writeByte(24)
|
||||
..write(obj.logoBase64);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -35,13 +35,15 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
|
||||
chkStripe: fields[19] as bool?,
|
||||
createdAt: fields[20] as DateTime?,
|
||||
updatedAt: fields[21] as DateTime?,
|
||||
chkMdpManuel: fields[22] as bool?,
|
||||
chkUsernameManuel: fields[23] as bool?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ClientModel obj) {
|
||||
writer
|
||||
..writeByte(22)
|
||||
..writeByte(24)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
@@ -85,7 +87,11 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
|
||||
..writeByte(20)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(21)
|
||||
..write(obj.updatedAt);
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(22)
|
||||
..write(obj.chkMdpManuel)
|
||||
..writeByte(23)
|
||||
..write(obj.chkUsernameManuel);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import 'package:connectivity_plus/src/connectivity_plus_web.dart';
|
||||
import 'package:geolocator_web/geolocator_web.dart';
|
||||
import 'package:image_picker_for_web/image_picker_for_web.dart';
|
||||
import 'package:package_info_plus/src/package_info_plus_web.dart';
|
||||
import 'package:shared_preferences_web/shared_preferences_web.dart';
|
||||
import 'package:url_launcher_web/url_launcher_web.dart';
|
||||
@@ -17,6 +18,7 @@ void registerPlugins([final Registrar? pluginRegistrar]) {
|
||||
final Registrar registrar = pluginRegistrar ?? webPluginRegistrar;
|
||||
ConnectivityPlusWebPlugin.registerWith(registrar);
|
||||
GeolocatorPlugin.registerWith(registrar);
|
||||
ImagePickerPlugin.registerWith(registrar);
|
||||
PackageInfoPlusWebPlugin.registerWith(registrar);
|
||||
SharedPreferencesPlugin.registerWith(registrar);
|
||||
UrlLauncherPlugin.registerWith(registrar);
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -86,6 +86,11 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/connectivity_plus_platform_interfa
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/connectivity_plus_platform_interface-2.0.1/lib/method_channel_connectivity.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/connectivity_plus_platform_interface-2.0.1/lib/src/enums.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/connectivity_plus_platform_interface-2.0.1/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/cross_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/src/types/base.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/src/types/html.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/src/web_helpers/web_helpers.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/src/x_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/crypto-3.0.6/lib/crypto.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/crypto-3.0.6/lib/src/digest.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/crypto-3.0.6/lib/src/digest_sink.dart
|
||||
@@ -103,36 +108,36 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/dart_polylabel2-1.0.0/lib/dart_pol
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dart_polylabel2-1.0.0/lib/src/impl.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dart_polylabel2-1.0.0/lib/src/point.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dart_polylabel2-1.0.0/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/dio.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/adapter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/adapters/browser_adapter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/cancel_token.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/compute/compute.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/compute/compute_web.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/dio.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/dio/dio_for_browser.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/dio_exception.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/dio_mixin.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/form_data.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/headers.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/interceptor.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/interceptors/imply_content_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/interceptors/log.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/multipart_file/browser_multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/parameter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/progress_stream/browser_progress_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/redirect_record.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/response/response_stream_handler.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/background_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/fused_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/sync_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/util/consolidate_bytes.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/util/transform_empty_to_null.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/dio.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/adapter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/adapters/browser_adapter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/cancel_token.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/compute/compute.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/compute/compute_web.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/dio.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/dio/dio_for_browser.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/dio_exception.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/dio_mixin.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/form_data.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/headers.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/interceptor.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/interceptors/imply_content_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/interceptors/log.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/multipart_file/browser_multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/parameter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/progress_stream/browser_progress_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/redirect_record.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/response/response_stream_handler.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/background_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/fused_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/sync_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/util/consolidate_bytes.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/util/transform_empty_to_null.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio_cache_interceptor-4.0.3/lib/dio_cache_interceptor.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio_cache_interceptor-4.0.3/lib/src/dio_cache_interceptor.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio_cache_interceptor-4.0.3/lib/src/dio_cache_interceptor_cache_utils.dart
|
||||
@@ -442,25 +447,25 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/hive_flutter-1.1.0/lib/src/hive_ex
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/hive_flutter-1.1.0/lib/src/stub/path.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/hive_flutter-1.1.0/lib/src/stub/path_provider.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/hive_flutter-1.1.0/lib/src/watch_box_builder.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/http.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/retry.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/abortable.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/base_client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/base_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/base_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/boundary_characters.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/browser_client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/byte_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/exception.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/multipart_file_stub.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/multipart_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/streamed_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/streamed_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/http.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/retry.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/abortable.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/base_client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/base_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/base_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/boundary_characters.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/browser_client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/byte_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/exception.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/multipart_file_stub.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/multipart_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/streamed_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/streamed_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_cache_core-1.1.1/lib/http_cache_core.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_cache_core-1.1.1/lib/src/model/cache/cache.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_cache_core-1.1.1/lib/src/model/cache/cache_cipher.dart
|
||||
@@ -497,6 +502,29 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/src/http_dat
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/src/media_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/src/scan.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker-1.1.2/lib/image_picker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/image_picker_for_web.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/src/image_resizer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/src/image_resizer_utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/src/pkg_web_tweaks.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/image_picker_platform_interface.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/method_channel/method_channel_image_picker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/platform_interface/image_picker_platform.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/camera_delegate.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/camera_device.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/image_options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/image_source.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/lost_data_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/media_options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/media_selection_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/multi_image_picker_options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/picked_file/base.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/picked_file/html.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/picked_file/lost_data.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/picked_file/picked_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/retrieve_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/types.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/lib/date_symbol_data_custom.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/lib/date_symbols.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/lib/intl.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/lib/number_symbols.dart
|
||||
@@ -615,6 +643,15 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/bb
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/lonlat.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/utm.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/mgrs.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/mime.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/bound_multipart_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/char_code.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/default_extension_map.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/extension.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/magic_number.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/mime_multipart_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/mime_shared.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/mime_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/package_info_plus.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_linux.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_web.dart
|
||||
@@ -736,129 +773,128 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/span_
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/string_scanner.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/string_scanner.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/charts.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/category_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/datetime_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/datetime_category_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/logarithmic_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/multi_level_labels.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/numeric_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/plot_band.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/base.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/behaviors/crosshair.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/behaviors/trackball.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/behaviors/zooming.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/cartesian_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/circular_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/annotation.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/callbacks.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/chart_point.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/circular_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/circular_data_label_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/connector_line.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/core_legend.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/core_tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/element_widget.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/empty_points.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/funnel_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/interactive_tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/layout_handler.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/legend.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/marker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/pyramid_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/title.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/funnel_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/accumulation_distribution_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/atr_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/bollinger_bands_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/ema_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/macd_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/momentum_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/roc_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/rsi_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/sma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/stochastic_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/technical_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/tma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/wma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/interactions/behavior.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/interactions/selection.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/interactions/tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/pyramid_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/box_and_whisker_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/bubble_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/candle_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/chart_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/doughnut_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/error_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/fast_line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/funnel_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/hilo_open_close_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/hilo_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/histogram_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/pie_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/pyramid_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/radial_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/range_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/range_column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/scatter_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/spline_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_area100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_bar100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_column100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_line100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/step_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stepline_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/waterfall_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/trendline/trendline.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/constants.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/enum.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/renderer_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/typedef.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/zooming_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/sparkline/marker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/sparkline/utils/enum.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/sparkline/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/core.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/localizations.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/calendar/calendar_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/calendar/hijri_date_time.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/license.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/localizations/global_localizations.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/slider_controller.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/assistview_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/barcodes_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/calendar_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/charts_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/chat_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/color_scheme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/datagrid_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/datapager_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/daterangepicker_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/gauges_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/maps_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/pdfviewer_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/range_selector_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/range_slider_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/slider_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/spark_charts_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/theme_widget.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/treemap_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/utils/shape_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/charts.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/category_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/datetime_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/datetime_category_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/logarithmic_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/multi_level_labels.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/numeric_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/plot_band.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/base.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/behaviors/crosshair.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/behaviors/trackball.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/behaviors/zooming.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/cartesian_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/circular_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/annotation.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/callbacks.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/chart_point.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/circular_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/circular_data_label_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/connector_line.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/core_legend.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/core_tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/element_widget.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/empty_points.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/funnel_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/interactive_tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/layout_handler.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/legend.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/marker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/pyramid_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/title.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/funnel_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/accumulation_distribution_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/atr_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/bollinger_bands_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/ema_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/macd_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/momentum_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/roc_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/rsi_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/sma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/stochastic_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/technical_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/tma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/wma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/interactions/behavior.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/interactions/selection.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/interactions/tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/pyramid_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/box_and_whisker_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/bubble_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/candle_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/chart_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/doughnut_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/error_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/fast_line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/funnel_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/hilo_open_close_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/hilo_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/histogram_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/pie_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/pyramid_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/radial_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/range_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/range_column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/scatter_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/spline_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_area100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_bar100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_column100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_line100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/step_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stepline_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/waterfall_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/trendline/trendline.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/constants.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/enum.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/renderer_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/typedef.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/zooming_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/sparkline/marker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/sparkline/utils/enum.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/sparkline/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/core.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/localizations.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/calendar/calendar_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/calendar/hijri_date_time.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/localizations/global_localizations.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/slider_controller.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/assistview_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/barcodes_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/calendar_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/charts_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/chat_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/color_scheme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/datagrid_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/datapager_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/daterangepicker_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/gauges_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/maps_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/pdfviewer_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/range_selector_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/range_slider_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/slider_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/spark_charts_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/theme_widget.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/treemap_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/utils/shape_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/lib/src/generated/ascii_glyph_set.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/lib/src/generated/glyph_set.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/lib/src/generated/top_level.dart
|
||||
@@ -951,6 +987,7 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/src/vector_m
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/src/vector_math_64/vector4.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/vector_math.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/vector_math_64.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/web-1.1.1/lib/helpers.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom/accelerometer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom/angle_instanced_arrays.dart
|
||||
@@ -1795,6 +1832,15 @@ file:///home/pierre/dev/flutter/packages/flutter/lib/src/widgets/widget_span.dar
|
||||
file:///home/pierre/dev/flutter/packages/flutter/lib/src/widgets/widget_state.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter/lib/src/widgets/will_pop_scope.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter/lib/widgets.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/flutter_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/cupertino_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/l10n/generated_date_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/l10n/generated_widgets_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/material_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/utils/date_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/widgets_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_web_plugins/lib/flutter_web_plugins.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_web_plugins/lib/src/navigation/utils.dart
|
||||
@@ -1858,6 +1904,7 @@ file:///home/pierre/dev/geosector/app/lib/core/services/hive_reset_state_service
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/hive_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/hive_web_fix.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/location_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/logger_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/sync_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/theme_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/theme/app_theme.dart
|
||||
@@ -1904,7 +1951,7 @@ file:///home/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_app_bar
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_layout.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/form_section.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/help_dialog.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/loading_progress_overlay.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/loading_spin_overlay.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/mapbox_map.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/membre_row_widget.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/membre_table_widget.dart
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -86,6 +86,11 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/connectivity_plus_platform_interfa
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/connectivity_plus_platform_interface-2.0.1/lib/method_channel_connectivity.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/connectivity_plus_platform_interface-2.0.1/lib/src/enums.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/connectivity_plus_platform_interface-2.0.1/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/cross_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/src/types/base.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/src/types/html.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/src/web_helpers/web_helpers.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/src/x_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/crypto-3.0.6/lib/crypto.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/crypto-3.0.6/lib/src/digest.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/crypto-3.0.6/lib/src/digest_sink.dart
|
||||
@@ -103,36 +108,36 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/dart_polylabel2-1.0.0/lib/dart_pol
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dart_polylabel2-1.0.0/lib/src/impl.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dart_polylabel2-1.0.0/lib/src/point.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dart_polylabel2-1.0.0/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/dio.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/adapter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/adapters/browser_adapter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/cancel_token.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/compute/compute.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/compute/compute_web.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/dio.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/dio/dio_for_browser.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/dio_exception.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/dio_mixin.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/form_data.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/headers.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/interceptor.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/interceptors/imply_content_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/interceptors/log.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/multipart_file/browser_multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/parameter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/progress_stream/browser_progress_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/redirect_record.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/response/response_stream_handler.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/background_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/fused_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/sync_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/util/consolidate_bytes.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/transformers/util/transform_empty_to_null.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/dio.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/adapter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/adapters/browser_adapter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/cancel_token.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/compute/compute.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/compute/compute_web.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/dio.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/dio/dio_for_browser.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/dio_exception.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/dio_mixin.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/form_data.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/headers.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/interceptor.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/interceptors/imply_content_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/interceptors/log.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/multipart_file/browser_multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/parameter.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/progress_stream/browser_progress_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/redirect_record.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/response/response_stream_handler.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/background_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/fused_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/sync_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/util/consolidate_bytes.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/transformers/util/transform_empty_to_null.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio_cache_interceptor-4.0.3/lib/dio_cache_interceptor.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio_cache_interceptor-4.0.3/lib/src/dio_cache_interceptor.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio_cache_interceptor-4.0.3/lib/src/dio_cache_interceptor_cache_utils.dart
|
||||
@@ -442,25 +447,25 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/hive_flutter-1.1.0/lib/src/hive_ex
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/hive_flutter-1.1.0/lib/src/stub/path.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/hive_flutter-1.1.0/lib/src/stub/path_provider.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/hive_flutter-1.1.0/lib/src/watch_box_builder.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/http.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/retry.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/abortable.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/base_client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/base_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/base_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/boundary_characters.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/browser_client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/byte_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/exception.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/multipart_file_stub.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/multipart_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/streamed_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/streamed_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/http.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/retry.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/abortable.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/base_client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/base_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/base_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/boundary_characters.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/browser_client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/byte_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/client.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/exception.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/multipart_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/multipart_file_stub.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/multipart_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/streamed_request.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/streamed_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_cache_core-1.1.1/lib/http_cache_core.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_cache_core-1.1.1/lib/src/model/cache/cache.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_cache_core-1.1.1/lib/src/model/cache/cache_cipher.dart
|
||||
@@ -497,6 +502,29 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/src/http_dat
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/src/media_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/src/scan.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker-1.1.2/lib/image_picker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/image_picker_for_web.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/src/image_resizer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/src/image_resizer_utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/src/pkg_web_tweaks.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/image_picker_platform_interface.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/method_channel/method_channel_image_picker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/platform_interface/image_picker_platform.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/camera_delegate.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/camera_device.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/image_options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/image_source.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/lost_data_response.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/media_options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/media_selection_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/multi_image_picker_options.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/picked_file/base.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/picked_file/html.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/picked_file/lost_data.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/picked_file/picked_file.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/retrieve_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/src/types/types.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/lib/date_symbol_data_custom.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/lib/date_symbols.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/lib/intl.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/lib/number_symbols.dart
|
||||
@@ -615,6 +643,15 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/bb
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/lonlat.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/utm.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/mgrs.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/mime.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/bound_multipart_stream.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/char_code.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/default_extension_map.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/extension.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/magic_number.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/mime_multipart_transformer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/mime_shared.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/mime-2.0.0/lib/src/mime_type.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/package_info_plus.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_linux.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_web.dart
|
||||
@@ -736,129 +773,128 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/span_
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/string_scanner.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/utils.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/string_scanner.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/charts.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/category_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/datetime_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/datetime_category_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/logarithmic_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/multi_level_labels.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/numeric_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/axis/plot_band.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/base.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/behaviors/crosshair.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/behaviors/trackball.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/behaviors/zooming.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/cartesian_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/circular_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/annotation.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/callbacks.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/chart_point.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/circular_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/circular_data_label_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/connector_line.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/core_legend.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/core_tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/element_widget.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/empty_points.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/funnel_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/interactive_tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/layout_handler.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/legend.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/marker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/pyramid_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/common/title.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/funnel_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/accumulation_distribution_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/atr_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/bollinger_bands_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/ema_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/macd_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/momentum_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/roc_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/rsi_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/sma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/stochastic_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/technical_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/tma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/indicators/wma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/interactions/behavior.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/interactions/selection.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/interactions/tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/pyramid_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/box_and_whisker_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/bubble_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/candle_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/chart_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/doughnut_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/error_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/fast_line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/funnel_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/hilo_open_close_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/hilo_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/histogram_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/pie_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/pyramid_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/radial_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/range_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/range_column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/scatter_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/spline_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_area100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_bar100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_column100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_line100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stacked_line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/step_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/stepline_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/series/waterfall_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/trendline/trendline.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/constants.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/enum.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/renderer_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/typedef.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/charts/utils/zooming_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/sparkline/marker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/sparkline/utils/enum.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/src/sparkline/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/core.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/localizations.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/calendar/calendar_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/calendar/hijri_date_time.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/license.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/localizations/global_localizations.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/slider_controller.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/assistview_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/barcodes_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/calendar_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/charts_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/chat_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/color_scheme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/datagrid_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/datapager_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/daterangepicker_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/gauges_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/maps_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/pdfviewer_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/range_selector_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/range_slider_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/slider_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/spark_charts_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/theme_widget.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/theme/treemap_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/src/utils/shape_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/charts.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/category_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/datetime_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/datetime_category_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/logarithmic_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/multi_level_labels.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/numeric_axis.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/axis/plot_band.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/base.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/behaviors/crosshair.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/behaviors/trackball.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/behaviors/zooming.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/cartesian_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/circular_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/annotation.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/callbacks.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/chart_point.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/circular_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/circular_data_label_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/connector_line.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/core_legend.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/core_tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/element_widget.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/empty_points.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/funnel_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/interactive_tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/layout_handler.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/legend.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/marker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/pyramid_data_label.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/common/title.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/funnel_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/accumulation_distribution_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/atr_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/bollinger_bands_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/ema_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/macd_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/momentum_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/roc_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/rsi_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/sma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/stochastic_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/technical_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/tma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/indicators/wma_indicator.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/interactions/behavior.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/interactions/selection.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/interactions/tooltip.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/pyramid_chart.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/box_and_whisker_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/bubble_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/candle_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/chart_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/doughnut_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/error_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/fast_line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/funnel_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/hilo_open_close_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/hilo_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/histogram_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/pie_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/pyramid_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/radial_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/range_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/range_column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/scatter_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/spline_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_area100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_bar100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_bar_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_column100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_column_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_line100_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stacked_line_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/step_area_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/stepline_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/series/waterfall_series.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/trendline/trendline.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/constants.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/enum.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/renderer_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/typedef.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/charts/utils/zooming_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/sparkline/marker.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/sparkline/utils/enum.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/src/sparkline/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/core.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/localizations.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/calendar/calendar_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/calendar/hijri_date_time.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/localizations/global_localizations.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/slider_controller.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/assistview_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/barcodes_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/calendar_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/charts_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/chat_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/color_scheme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/datagrid_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/datapager_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/daterangepicker_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/gauges_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/maps_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/pdfviewer_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/range_selector_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/range_slider_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/slider_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/spark_charts_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/theme_widget.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/theme/treemap_theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/utils/helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/src/utils/shape_helper.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/theme.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/lib/src/generated/ascii_glyph_set.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/lib/src/generated/glyph_set.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/lib/src/generated/top_level.dart
|
||||
@@ -951,6 +987,7 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/src/vector_m
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/src/vector_math_64/vector4.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/vector_math.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/vector_math_64.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/web-1.1.1/lib/helpers.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom/accelerometer.dart
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/web-1.1.1/lib/src/dom/angle_instanced_arrays.dart
|
||||
@@ -1794,6 +1831,15 @@ file:///home/pierre/dev/flutter/packages/flutter/lib/src/widgets/widget_span.dar
|
||||
file:///home/pierre/dev/flutter/packages/flutter/lib/src/widgets/widget_state.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter/lib/src/widgets/will_pop_scope.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter/lib/widgets.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/flutter_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/cupertino_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/l10n/generated_date_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/l10n/generated_widgets_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/material_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/utils/date_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/src/widgets_localizations.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_web_plugins/lib/flutter_web_plugins.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart
|
||||
file:///home/pierre/dev/flutter/packages/flutter_web_plugins/lib/src/navigation/utils.dart
|
||||
@@ -1857,6 +1903,7 @@ file:///home/pierre/dev/geosector/app/lib/core/services/hive_reset_state_service
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/hive_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/hive_web_fix.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/location_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/logger_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/sync_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/services/theme_service.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/core/theme/app_theme.dart
|
||||
@@ -1903,7 +1950,7 @@ file:///home/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_app_bar
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_layout.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/form_section.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/help_dialog.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/loading_progress_overlay.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/loading_spin_overlay.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/mapbox_map.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/membre_row_widget.dart
|
||||
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/membre_table_widget.dart
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import 'package:connectivity_plus/src/connectivity_plus_web.dart';
|
||||
import 'package:geolocator_web/geolocator_web.dart';
|
||||
import 'package:image_picker_for_web/image_picker_for_web.dart';
|
||||
import 'package:package_info_plus/src/package_info_plus_web.dart';
|
||||
import 'package:shared_preferences_web/shared_preferences_web.dart';
|
||||
import 'package:url_launcher_web/url_launcher_web.dart';
|
||||
@@ -17,6 +18,7 @@ void registerPlugins([final Registrar? pluginRegistrar]) {
|
||||
final Registrar registrar = pluginRegistrar ?? webPluginRegistrar;
|
||||
ConnectivityPlusWebPlugin.registerWith(registrar);
|
||||
GeolocatorPlugin.registerWith(registrar);
|
||||
ImagePickerPlugin.registerWith(registrar);
|
||||
PackageInfoPlusWebPlugin.registerWith(registrar);
|
||||
SharedPreferencesPlugin.registerWith(registrar);
|
||||
UrlLauncherPlugin.registerWith(registrar);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -87,7 +87,7 @@
|
||||
},
|
||||
{
|
||||
"name": "built_value",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/built_value-8.11.0",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/built_value-8.11.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
@@ -151,6 +151,12 @@
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "cross_file",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "crypto",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/crypto-3.0.6",
|
||||
@@ -195,7 +201,7 @@
|
||||
},
|
||||
{
|
||||
"name": "dio",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.18"
|
||||
},
|
||||
@@ -241,6 +247,30 @@
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "file_selector_linux",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.3+2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "file_selector_macos",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.4+3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "file_selector_platform_interface",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_platform_interface-2.6.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "file_selector_windows",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3+4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "fixnum",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/fixnum-1.1.1",
|
||||
@@ -295,6 +325,12 @@
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "flutter_localizations",
|
||||
"rootUri": "file:///home/pierre/dev/flutter/packages/flutter_localizations",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.7"
|
||||
},
|
||||
{
|
||||
"name": "flutter_map",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/flutter_map-8.2.1",
|
||||
@@ -307,6 +343,12 @@
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "flutter_plugin_android_lifecycle",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.29",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "flutter_svg",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/flutter_svg-2.2.0",
|
||||
@@ -435,7 +477,7 @@
|
||||
},
|
||||
{
|
||||
"name": "http",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
@@ -469,6 +511,54 @@
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "image_picker",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker-1.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "image_picker_android",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_android-0.8.12+25",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "image_picker_for_web",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "image_picker_ios",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "image_picker_linux",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1+2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "image_picker_macos",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1+2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "image_picker_platform_interface",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "image_picker_windows",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1+1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.19"
|
||||
},
|
||||
{
|
||||
"name": "intl",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2",
|
||||
@@ -717,7 +807,7 @@
|
||||
},
|
||||
{
|
||||
"name": "shared_preferences_android",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.10",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.11",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
@@ -819,13 +909,13 @@
|
||||
},
|
||||
{
|
||||
"name": "syncfusion_flutter_charts",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.7"
|
||||
},
|
||||
{
|
||||
"name": "syncfusion_flutter_core",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.7"
|
||||
},
|
||||
@@ -891,7 +981,7 @@
|
||||
},
|
||||
{
|
||||
"name": "url_launcher_android",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.16",
|
||||
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.17",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
|
||||
@@ -52,8 +52,8 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/built_collection-5.1.1/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/built_collection-5.1.1/lib/
|
||||
built_value
|
||||
3.0
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/built_value-8.11.0/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/built_value-8.11.0/lib/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/built_value-8.11.1/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/built_value-8.11.1/lib/
|
||||
characters
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/characters-1.4.0/
|
||||
@@ -94,6 +94,10 @@ convert
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/convert-3.1.2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/convert-3.1.2/lib/
|
||||
cross_file
|
||||
3.3
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/
|
||||
crypto
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/crypto-3.0.6/
|
||||
@@ -124,8 +128,8 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/dbus-0.7.11/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dbus-0.7.11/lib/
|
||||
dio
|
||||
2.18
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.8.0+1/lib/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio-5.9.0/lib/
|
||||
dio_cache_interceptor
|
||||
3.0
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/dio_cache_interceptor-4.0.3/
|
||||
@@ -154,6 +158,22 @@ file
|
||||
3.0
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file-7.0.1/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file-7.0.1/lib/
|
||||
file_selector_linux
|
||||
3.3
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.3+2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.3+2/lib/
|
||||
file_selector_macos
|
||||
3.6
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.4+3/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.4+3/lib/
|
||||
file_selector_platform_interface
|
||||
3.0
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_platform_interface-2.6.2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_platform_interface-2.6.2/lib/
|
||||
file_selector_windows
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3+4/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3+4/lib/
|
||||
fixnum
|
||||
3.1
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/fixnum-1.1.1/
|
||||
@@ -194,6 +214,10 @@ flutter_map_cache
|
||||
3.6
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/flutter_map_cache-2.0.0+1/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/flutter_map_cache-2.0.0+1/lib/
|
||||
flutter_plugin_android_lifecycle
|
||||
3.6
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.29/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.29/lib/
|
||||
flutter_svg
|
||||
3.6
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/flutter_svg-2.2.0/
|
||||
@@ -272,8 +296,8 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/html-0.15.6/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/html-0.15.6/lib/
|
||||
http
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0-beta.2/lib/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http-1.5.0/lib/
|
||||
http_cache_core
|
||||
3.0
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/http_cache_core-1.1.1/
|
||||
@@ -294,6 +318,38 @@ image
|
||||
3.0
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image-4.5.4/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image-4.5.4/lib/
|
||||
image_picker
|
||||
3.3
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker-1.1.2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker-1.1.2/lib/
|
||||
image_picker_android
|
||||
3.6
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_android-0.8.12+25/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_android-0.8.12+25/lib/
|
||||
image_picker_for_web
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.6/lib/
|
||||
image_picker_ios
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/lib/
|
||||
image_picker_linux
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1+2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1+2/lib/
|
||||
image_picker_macos
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1+2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1+2/lib/
|
||||
image_picker_platform_interface
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_platform_interface-2.10.1/lib/
|
||||
image_picker_windows
|
||||
2.19
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1+1/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1+1/lib/
|
||||
intl
|
||||
3.3
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/intl-0.20.2/
|
||||
@@ -460,8 +516,8 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences-2.5.3/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences-2.5.3/lib/
|
||||
shared_preferences_android
|
||||
3.6
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.10/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.10/lib/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.11/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.11/lib/
|
||||
shared_preferences_foundation
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/
|
||||
@@ -524,12 +580,12 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/
|
||||
syncfusion_flutter_charts
|
||||
3.7
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.1.42/lib/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.4/lib/
|
||||
syncfusion_flutter_core
|
||||
3.7
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.1.42/lib/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.4/lib/
|
||||
synchronized
|
||||
3.8
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/synchronized-3.4.0/
|
||||
@@ -572,8 +628,8 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher-6.3.2/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher-6.3.2/lib/
|
||||
url_launcher_android
|
||||
3.6
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.16/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.16/lib/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.17/
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.17/lib/
|
||||
url_launcher_ios
|
||||
3.4
|
||||
file:///home/pierre/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.3/
|
||||
@@ -670,6 +726,10 @@ flutter
|
||||
3.7
|
||||
file:///home/pierre/dev/flutter/packages/flutter/
|
||||
file:///home/pierre/dev/flutter/packages/flutter/lib/
|
||||
flutter_localizations
|
||||
3.7
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/
|
||||
file:///home/pierre/dev/flutter/packages/flutter_localizations/lib/
|
||||
flutter_test
|
||||
3.7
|
||||
file:///home/pierre/dev/flutter/packages/flutter_test/
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "geosector_app",
|
||||
"version": "3.0.4+304",
|
||||
"version": "3.0.6+306",
|
||||
"dependencies": [
|
||||
"connectivity_plus",
|
||||
"cupertino_icons",
|
||||
@@ -13,6 +13,7 @@
|
||||
"fl_chart",
|
||||
"flutter",
|
||||
"flutter_local_notifications",
|
||||
"flutter_localizations",
|
||||
"flutter_map",
|
||||
"flutter_map_cache",
|
||||
"flutter_svg",
|
||||
@@ -22,6 +23,7 @@
|
||||
"hive",
|
||||
"hive_flutter",
|
||||
"http_cache_file_store",
|
||||
"image_picker",
|
||||
"intl",
|
||||
"latlong2",
|
||||
"mqtt5_client",
|
||||
@@ -101,6 +103,20 @@
|
||||
"vm_service"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "image_picker",
|
||||
"version": "1.1.2",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"image_picker_android",
|
||||
"image_picker_for_web",
|
||||
"image_picker_ios",
|
||||
"image_picker_linux",
|
||||
"image_picker_macos",
|
||||
"image_picker_platform_interface",
|
||||
"image_picker_windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "universal_html",
|
||||
"version": "2.2.4",
|
||||
@@ -136,16 +152,6 @@
|
||||
"intl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "syncfusion_flutter_charts",
|
||||
"version": "30.1.42",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"intl",
|
||||
"syncfusion_flutter_core",
|
||||
"vector_math"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fl_chart",
|
||||
"version": "1.0.0",
|
||||
@@ -203,6 +209,21 @@
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter_localizations",
|
||||
"version": "0.0.0",
|
||||
"dependencies": [
|
||||
"characters",
|
||||
"clock",
|
||||
"collection",
|
||||
"flutter",
|
||||
"intl",
|
||||
"material_color_utilities",
|
||||
"meta",
|
||||
"path",
|
||||
"vector_math"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter",
|
||||
"version": "0.0.0",
|
||||
@@ -406,14 +427,6 @@
|
||||
"package_info_plus"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "syncfusion_flutter_core",
|
||||
"version": "30.1.42",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"vector_math"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "equatable",
|
||||
"version": "2.0.7",
|
||||
@@ -489,6 +502,24 @@
|
||||
"url_launcher_windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "syncfusion_flutter_charts",
|
||||
"version": "30.2.4",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"intl",
|
||||
"syncfusion_flutter_core",
|
||||
"vector_math"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "syncfusion_flutter_core",
|
||||
"version": "30.2.4",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"vector_math"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "connectivity_plus",
|
||||
"version": "6.1.4",
|
||||
@@ -560,6 +591,26 @@
|
||||
"yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "image_picker_windows",
|
||||
"version": "0.2.1+1",
|
||||
"dependencies": [
|
||||
"file_selector_platform_interface",
|
||||
"file_selector_windows",
|
||||
"flutter",
|
||||
"image_picker_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "image_picker_platform_interface",
|
||||
"version": "2.10.1",
|
||||
"dependencies": [
|
||||
"cross_file",
|
||||
"flutter",
|
||||
"http",
|
||||
"plugin_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "typed_data",
|
||||
"version": "1.4.0",
|
||||
@@ -567,6 +618,13 @@
|
||||
"collection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "plugin_platform_interface",
|
||||
"version": "2.1.8",
|
||||
"dependencies": [
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "http_cache_core",
|
||||
"version": "1.1.1",
|
||||
@@ -656,16 +714,6 @@
|
||||
"unicode"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "http",
|
||||
"version": "1.5.0-beta.2",
|
||||
"dependencies": [
|
||||
"async",
|
||||
"http_parser",
|
||||
"meta",
|
||||
"web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dart_earcut",
|
||||
"version": "1.2.0",
|
||||
@@ -701,6 +749,26 @@
|
||||
"version": "2.7.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "image_picker_macos",
|
||||
"version": "0.2.1+2",
|
||||
"dependencies": [
|
||||
"file_selector_macos",
|
||||
"file_selector_platform_interface",
|
||||
"flutter",
|
||||
"image_picker_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "image_picker_linux",
|
||||
"version": "0.2.1+2",
|
||||
"dependencies": [
|
||||
"file_selector_linux",
|
||||
"file_selector_platform_interface",
|
||||
"flutter",
|
||||
"image_picker_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "csslib",
|
||||
"version": "1.0.2",
|
||||
@@ -731,6 +799,16 @@
|
||||
"version": "1.1.1",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "http",
|
||||
"version": "1.5.0",
|
||||
"dependencies": [
|
||||
"async",
|
||||
"http_parser",
|
||||
"meta",
|
||||
"web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "url_launcher_platform_interface",
|
||||
"version": "2.3.2",
|
||||
@@ -739,13 +817,6 @@
|
||||
"plugin_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "plugin_platform_interface",
|
||||
"version": "2.1.8",
|
||||
"dependencies": [
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider_windows",
|
||||
"version": "2.3.0",
|
||||
@@ -955,6 +1026,33 @@
|
||||
"url_launcher_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "xdg_directories",
|
||||
"version": "1.1.0",
|
||||
"dependencies": [
|
||||
"meta",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "file_selector_linux",
|
||||
"version": "0.9.3+2",
|
||||
"dependencies": [
|
||||
"cross_file",
|
||||
"file_selector_platform_interface",
|
||||
"flutter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "file_selector_platform_interface",
|
||||
"version": "2.6.2",
|
||||
"dependencies": [
|
||||
"cross_file",
|
||||
"flutter",
|
||||
"http",
|
||||
"plugin_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "http_parser",
|
||||
"version": "4.1.2",
|
||||
@@ -965,14 +1063,6 @@
|
||||
"typed_data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "xdg_directories",
|
||||
"version": "1.1.0",
|
||||
"dependencies": [
|
||||
"meta",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "crypto",
|
||||
"version": "3.0.6",
|
||||
@@ -1094,6 +1184,22 @@
|
||||
"path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "image_picker_for_web",
|
||||
"version": "3.0.6",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"flutter_web_plugins",
|
||||
"image_picker_platform_interface",
|
||||
"mime",
|
||||
"web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mime",
|
||||
"version": "2.0.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"version": "3.1.6",
|
||||
@@ -1151,6 +1257,15 @@
|
||||
"web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "file_selector_macos",
|
||||
"version": "0.9.4+3",
|
||||
"dependencies": [
|
||||
"cross_file",
|
||||
"file_selector_platform_interface",
|
||||
"flutter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "logger",
|
||||
"version": "2.6.1",
|
||||
@@ -1203,15 +1318,24 @@
|
||||
"vector_graphics_codec"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider_android",
|
||||
"version": "2.2.17",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"path_provider_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dio",
|
||||
"version": "5.8.0+1",
|
||||
"version": "5.9.0",
|
||||
"dependencies": [
|
||||
"async",
|
||||
"collection",
|
||||
"dio_web_adapter",
|
||||
"http_parser",
|
||||
"meta",
|
||||
"mime",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
@@ -1225,17 +1349,9 @@
|
||||
"web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider_android",
|
||||
"version": "2.2.17",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"path_provider_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "shared_preferences_android",
|
||||
"version": "2.4.10",
|
||||
"version": "2.4.11",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"shared_preferences_platform_interface"
|
||||
@@ -1434,16 +1550,6 @@
|
||||
"path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "built_value",
|
||||
"version": "8.11.0",
|
||||
"dependencies": [
|
||||
"built_collection",
|
||||
"collection",
|
||||
"fixnum",
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "watcher",
|
||||
"version": "1.1.2",
|
||||
@@ -1484,6 +1590,16 @@
|
||||
"collection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "built_value",
|
||||
"version": "8.11.1",
|
||||
"dependencies": [
|
||||
"built_collection",
|
||||
"collection",
|
||||
"fixnum",
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stream_transform",
|
||||
"version": "2.1.1",
|
||||
@@ -1540,11 +1656,6 @@
|
||||
"web_socket_channel"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mime",
|
||||
"version": "2.0.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "js",
|
||||
"version": "0.7.2",
|
||||
@@ -1602,6 +1713,14 @@
|
||||
"stream_channel"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "image_picker_ios",
|
||||
"version": "0.8.12+2",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"image_picker_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "win32",
|
||||
"version": "5.14.0",
|
||||
@@ -1609,13 +1728,46 @@
|
||||
"ffi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "file_selector_windows",
|
||||
"version": "0.9.3+4",
|
||||
"dependencies": [
|
||||
"cross_file",
|
||||
"file_selector_platform_interface",
|
||||
"flutter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cross_file",
|
||||
"version": "0.3.4+2",
|
||||
"dependencies": [
|
||||
"meta",
|
||||
"web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "url_launcher_android",
|
||||
"version": "6.3.16",
|
||||
"version": "6.3.17",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"url_launcher_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "image_picker_android",
|
||||
"version": "0.8.12+25",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"flutter_plugin_android_lifecycle",
|
||||
"image_picker_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter_plugin_android_lifecycle",
|
||||
"version": "2.0.29",
|
||||
"dependencies": [
|
||||
"flutter"
|
||||
]
|
||||
}
|
||||
],
|
||||
"configVersion": 1
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -96,6 +96,7 @@ GEOSECTOR est une solution complète développée en Flutter qui révolutionne l
|
||||
| **Géolocalisation** | Geolocator | 10.1.0 | Services de localisation |
|
||||
| **Chat** | MQTT5 Client | 4.2.0 | Messagerie temps réel |
|
||||
| **UI** | Material Design 3 | Native | Composants d'interface |
|
||||
| **Logging** | LoggerService | Custom | Logs conditionnels par env |
|
||||
|
||||
### 🏛️ Architecture en couches
|
||||
|
||||
@@ -423,6 +424,355 @@ Interface : Affichage utilisateur via ApiException.showError()
|
||||
Network Errors : "Problème de connexion réseau"
|
||||
Timeout : "Délai d'attente dépassé"
|
||||
|
||||
## 🔧 Pattern de gestion des erreurs API dans les Repositories
|
||||
|
||||
### 🎯 Problème à résoudre
|
||||
|
||||
Les messages d'erreur spécifiques de l'API (comme "Cet email est déjà utilisé") n'étaient pas affichés à l'utilisateur. À la place, un message générique "Erreur inattendue" apparaissait.
|
||||
|
||||
### 📝 Modifications requises
|
||||
|
||||
#### **1. ApiService - Conversion automatique des DioException**
|
||||
|
||||
Toutes les méthodes HTTP génériques doivent convertir les `DioException` en `ApiException` :
|
||||
|
||||
```dart
|
||||
// ✅ CORRECT - ApiService avec conversion automatique
|
||||
Future<Response> put(String path, {dynamic data}) async {
|
||||
try {
|
||||
return await _dio.put(path, data: data);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e); // ← Extraction automatique du message
|
||||
} catch (e) {
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête PUT', originalError: e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Appliquer le même pattern pour `post()`, `get()`, `delete()`.
|
||||
|
||||
#### **2. Repository - Simplification de la gestion des erreurs**
|
||||
|
||||
```dart
|
||||
// ❌ INCORRECT - Code inutile qui ne sera jamais exécuté
|
||||
Future<bool> updateMembre(MembreModel membre) async {
|
||||
try {
|
||||
final response = await ApiService.instance.put('/users/${membre.id}', data: data);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
await saveMembreBox(membre);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ⚠️ CE CODE NE SERA JAMAIS ATTEINT car Dio lance une exception pour les codes d'erreur
|
||||
if (response.data != null && response.data is Map<String, dynamic>) {
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
if (responseData['status'] == 'error') {
|
||||
throw Exception(responseData['message']);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```dart
|
||||
// ✅ CORRECT - Code simplifié et fonctionnel
|
||||
Future<bool> updateMembre(MembreModel membre) async {
|
||||
try {
|
||||
final response = await ApiService.instance.put('/users/${membre.id}', data: data);
|
||||
|
||||
// Si on arrive ici, c'est que la requête a réussi (200)
|
||||
await saveMembreBox(membre);
|
||||
return true;
|
||||
|
||||
} catch (e) {
|
||||
// L'ApiException contient déjà le message extrait de l'API
|
||||
rethrow; // Propager l'exception pour affichage
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **3. Amélioration des logs (avec LoggerService)**
|
||||
|
||||
```dart
|
||||
// ✅ CORRECT - Logs propres sans détails techniques
|
||||
catch (e) {
|
||||
// Ne pas logger les détails techniques de DioException
|
||||
if (e is ApiException) {
|
||||
LoggerService.error('Erreur lors de la mise à jour: ${e.message}');
|
||||
} else {
|
||||
LoggerService.error('Erreur lors de la mise à jour');
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
```
|
||||
|
||||
N'oubliez pas d'importer `ApiException` :
|
||||
```dart
|
||||
import 'package:geosector_app/core/utils/api_exception.dart';
|
||||
```
|
||||
|
||||
### 🔄 Flux d'erreur corrigé
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant API as API Server
|
||||
participant Dio as Dio Client
|
||||
participant AS as ApiService
|
||||
participant AE as ApiException
|
||||
participant R as Repository
|
||||
participant UI as Interface
|
||||
|
||||
UI->>R: updateData()
|
||||
R->>AS: put('/endpoint')
|
||||
AS->>Dio: HTTP PUT
|
||||
Dio->>API: Request
|
||||
|
||||
alt Code d'erreur (409, 400, etc.)
|
||||
API-->>Dio: Error + JSON body
|
||||
Note over API: {"status": "error",<br/>"message": "Message spécifique"}
|
||||
|
||||
Dio-->>AS: DioException
|
||||
AS->>AE: fromDioException()
|
||||
Note over AE: Extrait message<br/>depuis response.data
|
||||
AE-->>AS: ApiException("Message spécifique")
|
||||
AS-->>R: throw ApiException
|
||||
R-->>UI: throw ApiException
|
||||
UI->>UI: showError("Message spécifique")
|
||||
else Succès (200, 201)
|
||||
API-->>Dio: Success
|
||||
Dio-->>AS: Response
|
||||
AS-->>R: Response
|
||||
R->>R: Sauvegarde Hive
|
||||
R-->>UI: return true
|
||||
end
|
||||
```
|
||||
|
||||
### ✅ Checklist de migration
|
||||
|
||||
Pour chaque repository :
|
||||
|
||||
- [ ] Vérifier que l'ApiService convertit les DioException en ApiException
|
||||
- [ ] Simplifier le code : supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Propager les exceptions avec `rethrow`
|
||||
- [ ] Améliorer les logs pour ne pas afficher les détails techniques
|
||||
- [ ] Importer `ApiException` si nécessaire
|
||||
- [ ] Tester avec une erreur 409 pour vérifier l'affichage du message
|
||||
|
||||
### 📊 Résultat attendu
|
||||
|
||||
| Avant | Après |
|
||||
|-------|-------|
|
||||
| "Erreur inattendue" | "Cet email est déjà utilisé par un autre utilisateur" |
|
||||
| Logs avec stack trace Dio | Message d'erreur simple et clair |
|
||||
| Code complexe avec vérifications inutiles | Code simplifié et maintenable |
|
||||
|
||||
Cette approche garantit que tous les messages d'erreur de l'API sont correctement affichés à l'utilisateur, améliorant ainsi l'expérience utilisateur et facilitant le débogage.
|
||||
|
||||
## 📝 Service de Logging Intelligent
|
||||
|
||||
### 🎯 Vue d'ensemble
|
||||
|
||||
GEOSECTOR v2.0 implémente un **LoggerService centralisé** qui désactive automatiquement les logs de debug en production, optimisant ainsi les performances et la sécurité tout en facilitant le développement.
|
||||
|
||||
### 🔍 Détection automatique de l'environnement
|
||||
|
||||
Le LoggerService détecte automatiquement l'environnement d'exécution :
|
||||
|
||||
```dart
|
||||
// Détection basée sur l'URL pour le web
|
||||
if (currentUrl.contains('dapp.geosector.fr')) → DEV
|
||||
if (currentUrl.contains('rapp.geosector.fr')) → REC
|
||||
Sinon → PROD
|
||||
|
||||
// Pour mobile/desktop : utilise kReleaseMode de Flutter
|
||||
```
|
||||
|
||||
**Comportement par environnement :**
|
||||
|
||||
| Environnement | Logs Debug | Logs Erreur | Stack Traces |
|
||||
|---------------|------------|-------------|--------------|
|
||||
| **DEV** | ✅ Activés | ✅ Activés | ✅ Complètes |
|
||||
| **REC** | ✅ Activés | ✅ Activés | ✅ Complètes |
|
||||
| **PROD** | ❌ Désactivés | ✅ Activés | ❌ Masquées |
|
||||
|
||||
### 🛠️ API du LoggerService
|
||||
|
||||
#### **Méthodes principales**
|
||||
|
||||
```dart
|
||||
// Remplacement direct de debugPrint
|
||||
LoggerService.log('Message simple');
|
||||
|
||||
// Logs catégorisés avec emojis automatiques
|
||||
LoggerService.info('Information'); // ℹ️
|
||||
LoggerService.success('Opération réussie'); // ✅
|
||||
LoggerService.warning('Attention'); // ⚠️
|
||||
LoggerService.error('Erreur', exception); // ❌ (toujours affiché)
|
||||
LoggerService.debug('Debug', emoji: '🔧'); // 🔧
|
||||
|
||||
// Logs spécialisés
|
||||
LoggerService.api('Requête API envoyée'); // 🔗
|
||||
LoggerService.database('Box Hive ouverte'); // 💾
|
||||
LoggerService.navigation('Route: /admin'); // 🧭
|
||||
LoggerService.performance('Temps: 45ms'); // ⏱️
|
||||
```
|
||||
|
||||
#### **Fonctionnalités avancées**
|
||||
|
||||
```dart
|
||||
// Logs groupés pour améliorer la lisibilité
|
||||
LoggerService.group('Traitement utilisateur', [
|
||||
'Validation des données',
|
||||
'Appel API',
|
||||
'Sauvegarde locale',
|
||||
'Notification envoyée'
|
||||
]);
|
||||
// Affiche :
|
||||
// ┌─ Traitement utilisateur
|
||||
// ├─ Validation des données
|
||||
// ├─ Appel API
|
||||
// ├─ Sauvegarde locale
|
||||
// └─ Notification envoyée
|
||||
|
||||
// Log JSON formaté
|
||||
LoggerService.json('Payload API', {
|
||||
'id': 123,
|
||||
'name': 'Test',
|
||||
'active': true
|
||||
});
|
||||
// Affiche :
|
||||
// 📋 Payload API:
|
||||
// id: 123
|
||||
// name: Test
|
||||
// active: true
|
||||
|
||||
// Log conditionnel
|
||||
LoggerService.conditional(
|
||||
'Debug spécifique',
|
||||
condition: user.role > 2
|
||||
);
|
||||
```
|
||||
|
||||
### 🔄 Migration depuis debugPrint
|
||||
|
||||
#### **Avant (debugPrint partout)**
|
||||
|
||||
```dart
|
||||
debugPrint('🔄 Début de la création d\'un nouveau membre');
|
||||
debugPrint('📤 Données envoyées à l\'API: $data');
|
||||
debugPrint('❌ Erreur: $e');
|
||||
```
|
||||
|
||||
#### **Après (LoggerService)**
|
||||
|
||||
```dart
|
||||
LoggerService.info('Début de la création d\'un nouveau membre');
|
||||
LoggerService.api('Données envoyées à l\'API: $data');
|
||||
LoggerService.error('Erreur lors de la création', e);
|
||||
```
|
||||
|
||||
### 📋 Utilisation avec les extensions
|
||||
|
||||
Pour une syntaxe encore plus concise, utilisez les extensions String :
|
||||
|
||||
```dart
|
||||
// Import nécessaire
|
||||
import 'package:geosector_app/core/services/logger_service.dart';
|
||||
|
||||
// Utilisation directe sur les strings
|
||||
'Connexion réussie'.logSuccess();
|
||||
'Erreur de validation'.logError(exception);
|
||||
'Requête GET /users'.logApi();
|
||||
'Box membres ouverte'.logDatabase();
|
||||
```
|
||||
|
||||
### 🎯 Bonnes pratiques
|
||||
|
||||
#### **1. Catégorisation des logs**
|
||||
|
||||
```dart
|
||||
// ✅ BON - Log catégorisé et clair
|
||||
LoggerService.api('POST /users - Création membre');
|
||||
LoggerService.database('Sauvegarde dans Box membres');
|
||||
|
||||
// ❌ MAUVAIS - Log générique sans contexte
|
||||
debugPrint('Traitement en cours...');
|
||||
```
|
||||
|
||||
#### **2. Gestion des erreurs**
|
||||
|
||||
```dart
|
||||
try {
|
||||
await operation();
|
||||
LoggerService.success('Opération terminée');
|
||||
} catch (e, stackTrace) {
|
||||
// Les erreurs sont TOUJOURS loggées, même en PROD
|
||||
LoggerService.error('Échec de l\'opération', e, stackTrace);
|
||||
}
|
||||
```
|
||||
|
||||
#### **3. Logs de performance**
|
||||
|
||||
```dart
|
||||
final stopwatch = Stopwatch()..start();
|
||||
await heavyOperation();
|
||||
stopwatch.stop();
|
||||
LoggerService.performance('Opération lourde: ${stopwatch.elapsedMilliseconds}ms');
|
||||
```
|
||||
|
||||
### 🔒 Sécurité et performances
|
||||
|
||||
#### **Avantages en production**
|
||||
|
||||
1. **Sécurité** : Aucune information sensible exposée dans la console
|
||||
2. **Performance** : Pas d'appels inutiles à debugPrint
|
||||
3. **Taille** : Bundle JavaScript plus léger (tree shaking)
|
||||
4. **Professionnalisme** : Console propre pour les utilisateurs finaux
|
||||
|
||||
#### **Conservation des logs d'erreur**
|
||||
|
||||
Les erreurs restent visibles en production pour faciliter le support :
|
||||
|
||||
```dart
|
||||
// Toujours affiché, même en PROD
|
||||
LoggerService.error('Erreur critique détectée', error);
|
||||
// Mais sans la stack trace complète qui pourrait révéler
|
||||
// des détails d'implémentation
|
||||
```
|
||||
|
||||
### 📊 Impact sur le codebase
|
||||
|
||||
| Métrique | Avant | Après |
|
||||
|----------|-------|-------|
|
||||
| **Logs en PROD** | Tous visibles | Erreurs uniquement |
|
||||
| **Performance web** | debugPrint actifs | Désactivés automatiquement |
|
||||
| **Maintenance** | debugPrint dispersés | Service centralisé |
|
||||
| **Lisibilité** | Emojis manuels | Catégorisation automatique |
|
||||
|
||||
### 🚀 Configuration et initialisation
|
||||
|
||||
Le LoggerService fonctionne automatiquement sans configuration :
|
||||
|
||||
```dart
|
||||
// main.dart - Aucune initialisation requise
|
||||
void main() async {
|
||||
// LoggerService détecte automatiquement l'environnement
|
||||
// via ApiService.getCurrentEnvironment()
|
||||
|
||||
runApp(GeosectorApp());
|
||||
}
|
||||
|
||||
// Utilisation immédiate dans n'importe quel fichier
|
||||
LoggerService.info('Application démarrée');
|
||||
```
|
||||
|
||||
Cette architecture garantit un système de logging professionnel, sécurisé et performant, adapté aux besoins de développement tout en protégeant la production. 📝✨
|
||||
|
||||
## 🎯 Gestion des rôles
|
||||
|
||||
### Hiérarchie des permissions
|
||||
|
||||
766
app/TODO-APP.md
Normal file
766
app/TODO-APP.md
Normal file
@@ -0,0 +1,766 @@
|
||||
# TODO-APP.md
|
||||
|
||||
## 📋 Liste des tâches à effectuer sur l'application GEOSECTOR
|
||||
|
||||
### 🔧 Migration du pattern de gestion des erreurs API
|
||||
|
||||
#### 🎯 Objectif
|
||||
Appliquer le nouveau pattern de gestion des erreurs pour que tous les messages spécifiques de l'API soient correctement affichés aux utilisateurs (au lieu du message générique "Erreur inattendue").
|
||||
|
||||
---
|
||||
|
||||
### ✅ Repositories déjà migrés
|
||||
- [x] **MembreRepository** (`lib/core/repositories/membre_repository.dart`)
|
||||
- ✅ Conversion DioException → ApiException
|
||||
- ✅ Simplification du code
|
||||
- ✅ Logs avec LoggerService
|
||||
|
||||
---
|
||||
|
||||
### 📝 Repositories à migrer
|
||||
|
||||
#### 1. **UserRepository** (`lib/core/repositories/user_repository.dart`)
|
||||
- [ ] Vérifier/ajouter l'import `ApiException`
|
||||
- [ ] Simplifier `updateUser()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `syncUser()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `syncAllUsers()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Remplacer `debugPrint` par `LoggerService`
|
||||
- [ ] Améliorer la gestion des erreurs dans le bloc catch (ne pas logger DioException)
|
||||
|
||||
#### 2. **OperationRepository** (`lib/core/repositories/operation_repository.dart`)
|
||||
- [ ] Vérifier/ajouter l'import `ApiException`
|
||||
- [ ] Simplifier `createOperation()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `updateOperation()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `deleteOperation()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `activateOperation()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Remplacer `debugPrint` par `LoggerService`
|
||||
- [ ] Améliorer la gestion des erreurs dans le bloc catch (ne pas logger DioException)
|
||||
|
||||
#### 3. **PassageRepository** (`lib/core/repositories/passage_repository.dart`)
|
||||
- [ ] Vérifier/ajouter l'import `ApiException`
|
||||
- [ ] Simplifier `createPassage()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `updatePassage()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `updatePassageStatus()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `syncPassages()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Remplacer `debugPrint` par `LoggerService`
|
||||
- [ ] Améliorer la gestion des erreurs dans le bloc catch (ne pas logger DioException)
|
||||
|
||||
#### 4. **SectorRepository** (`lib/core/repositories/sector_repository.dart`)
|
||||
- [ ] Vérifier/ajouter l'import `ApiException`
|
||||
- [ ] Simplifier `createSector()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `updateSector()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `deleteSector()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `assignUserToSector()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Remplacer `debugPrint` par `LoggerService`
|
||||
- [ ] Améliorer la gestion des erreurs dans le bloc catch (ne pas logger DioException)
|
||||
|
||||
#### 5. **AmicaleRepository** (`lib/core/repositories/amicale_repository.dart`)
|
||||
- [ ] Vérifier/ajouter l'import `ApiException`
|
||||
- [ ] Simplifier `updateAmicale()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `createAmicale()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Simplifier `syncAmicales()` - supprimer les vérifications de statut après l'appel API
|
||||
- [ ] Remplacer `debugPrint` par `LoggerService`
|
||||
- [ ] Améliorer la gestion des erreurs dans le bloc catch (ne pas logger DioException)
|
||||
|
||||
---
|
||||
|
||||
### 🛠️ Pattern à appliquer
|
||||
|
||||
#### ❌ Code à supprimer (ancien pattern)
|
||||
```dart
|
||||
try {
|
||||
final response = await ApiService.instance.put('/endpoint', data: data);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Traitement succès
|
||||
return true;
|
||||
}
|
||||
|
||||
// ⚠️ Ce code ne sera JAMAIS exécuté
|
||||
if (response.data != null && response.data is Map<String, dynamic>) {
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
if (responseData['status'] == 'error') {
|
||||
throw Exception(responseData['message']);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur: $e');
|
||||
rethrow;
|
||||
}
|
||||
```
|
||||
|
||||
#### ✅ Nouveau pattern à utiliser
|
||||
```dart
|
||||
try {
|
||||
final response = await ApiService.instance.put('/endpoint', data: data);
|
||||
|
||||
// Si on arrive ici, la requête a réussi (200/201)
|
||||
// Traitement succès
|
||||
return true;
|
||||
|
||||
} catch (e) {
|
||||
// Log propre sans détails techniques
|
||||
if (e is ApiException) {
|
||||
LoggerService.error('Erreur lors de l\'opération: ${e.message}');
|
||||
} else {
|
||||
LoggerService.error('Erreur lors de l\'opération');
|
||||
}
|
||||
rethrow; // Propager pour affichage UI
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📦 Imports nécessaires
|
||||
|
||||
Ajouter ces imports dans chaque repository si manquants :
|
||||
|
||||
```dart
|
||||
import 'package:geosector_app/core/services/logger_service.dart';
|
||||
import 'package:geosector_app/core/utils/api_exception.dart';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🧪 Tests de validation
|
||||
|
||||
Pour chaque repository migré, tester :
|
||||
|
||||
1. **Test d'erreur 409 (Conflict)** :
|
||||
- Créer/modifier avec un email déjà existant
|
||||
- Vérifier que le message "Cet email est déjà utilisé" s'affiche
|
||||
|
||||
2. **Test d'erreur 400 (Bad Request)** :
|
||||
- Envoyer des données invalides
|
||||
- Vérifier que le message d'erreur spécifique s'affiche
|
||||
|
||||
3. **Test d'erreur réseau** :
|
||||
- Couper la connexion
|
||||
- Vérifier que "Problème de connexion réseau" s'affiche
|
||||
|
||||
4. **Test de succès** :
|
||||
- Opération normale
|
||||
- Vérifier que tout fonctionne comme avant
|
||||
|
||||
---
|
||||
|
||||
### 📊 Critères de succès
|
||||
|
||||
- [ ] Tous les repositories utilisent le nouveau pattern
|
||||
- [ ] Plus aucun `debugPrint` dans les repositories (remplacé par `LoggerService`)
|
||||
- [ ] Les messages d'erreur spécifiques de l'API s'affichent correctement
|
||||
- [ ] Les logs en production sont désactivés (sauf erreurs)
|
||||
- [ ] Le code est plus simple et maintenable
|
||||
|
||||
---
|
||||
|
||||
### 🔍 Commandes utiles pour vérifier
|
||||
|
||||
```bash
|
||||
# Rechercher les anciens patterns à remplacer
|
||||
grep -r "response.statusCode ==" lib/core/repositories/
|
||||
grep -r "responseData\['status'\] == 'error'" lib/core/repositories/
|
||||
grep -r "debugPrint" lib/core/repositories/
|
||||
|
||||
# Vérifier les imports manquants
|
||||
grep -L "LoggerService" lib/core/repositories/*.dart
|
||||
grep -L "ApiException" lib/core/repositories/*.dart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📅 Priorité
|
||||
|
||||
1. **Haute** : UserRepository (utilisé partout)
|
||||
2. **Haute** : OperationRepository (fonctionnalité critique)
|
||||
3. **Moyenne** : PassageRepository (utilisé fréquemment)
|
||||
4. **Moyenne** : AmicaleRepository (gestion administrative)
|
||||
5. **Basse** : SectorRepository (moins critique)
|
||||
|
||||
---
|
||||
|
||||
### 📝 Notes
|
||||
|
||||
- Cette migration améliore l'UX en affichant des messages d'erreur clairs
|
||||
- Le LoggerService désactive automatiquement les logs en production
|
||||
- Le code devient plus maintenable et cohérent
|
||||
- Documenter dans README-APP.md une fois terminé
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests unitaires Flutter à implémenter
|
||||
|
||||
### 📁 Structure des tests
|
||||
|
||||
Le dossier `/test` doit contenir des tests unitaires pour valider le comportement de l'application.
|
||||
|
||||
### 📝 Tests prioritaires à créer
|
||||
|
||||
#### 1. **Tests des Repositories** (`test/repositories/`)
|
||||
|
||||
##### `test/repositories/membre_repository_test.dart`
|
||||
- [ ] Test de création d'un membre avec succès
|
||||
- [ ] Test de création avec email déjà existant (409)
|
||||
- [ ] Test de mise à jour d'un membre
|
||||
- [ ] Test de suppression d'un membre
|
||||
- [ ] Test de gestion des erreurs réseau
|
||||
- [ ] Test du cache Hive local
|
||||
|
||||
##### `test/repositories/user_repository_test.dart`
|
||||
- [ ] Test de connexion réussie
|
||||
- [ ] Test de connexion avec mauvais identifiants
|
||||
- [ ] Test de mise à jour du profil utilisateur
|
||||
- [ ] Test de synchronisation des données
|
||||
- [ ] Test de gestion de session
|
||||
|
||||
##### `test/repositories/operation_repository_test.dart`
|
||||
- [ ] Test de création d'opération
|
||||
- [ ] Test d'activation/désactivation
|
||||
- [ ] Test de récupération des opérations par amicale
|
||||
- [ ] Test de suppression avec transfert de passages
|
||||
|
||||
#### 2. **Tests des Services** (`test/services/`)
|
||||
|
||||
##### `test/services/api_service_test.dart`
|
||||
- [ ] Test de détection d'environnement (DEV/REC/PROD)
|
||||
- [ ] Test de gestion des sessions
|
||||
- [ ] Test de conversion DioException vers ApiException
|
||||
- [ ] Test des différents codes d'erreur HTTP
|
||||
- [ ] Test du retry automatique
|
||||
|
||||
##### `test/services/logger_service_test.dart`
|
||||
- [ ] Test de désactivation des logs en PROD
|
||||
- [ ] Test des différents niveaux de log
|
||||
- [ ] Test du formatage des messages
|
||||
|
||||
##### `test/services/hive_service_test.dart`
|
||||
- [ ] Test d'initialisation des boîtes Hive
|
||||
- [ ] Test de sauvegarde et récupération
|
||||
- [ ] Test de suppression de données
|
||||
- [ ] Test de migration de schéma
|
||||
|
||||
#### 3. **Tests des Models** (`test/models/`)
|
||||
|
||||
##### `test/models/membre_model_test.dart`
|
||||
- [ ] Test de sérialisation JSON
|
||||
- [ ] Test de désérialisation JSON
|
||||
- [ ] Test de conversion vers UserModel
|
||||
- [ ] Test des valeurs par défaut
|
||||
|
||||
##### `test/models/amicale_model_test.dart`
|
||||
- [ ] Test de sérialisation/désérialisation
|
||||
- [ ] Test des nouveaux champs booléens (chk_mdp_manuel, chk_username_manuel)
|
||||
- [ ] Test de validation des données
|
||||
|
||||
#### 4. **Tests des Widgets** (`test/widgets/`)
|
||||
|
||||
##### `test/widgets/user_form_test.dart`
|
||||
- [ ] Test d'affichage conditionnel des champs (username/password)
|
||||
- [ ] Test de validation du mot de passe (regex)
|
||||
- [ ] Test de validation de l'username (pas d'espaces)
|
||||
- [ ] Test de génération automatique d'username
|
||||
- [ ] Test du mode création vs modification
|
||||
|
||||
##### `test/widgets/membre_table_widget_test.dart`
|
||||
- [ ] Test d'affichage responsive (mobile vs web)
|
||||
- [ ] Test de masquage des colonnes sur mobile
|
||||
- [ ] Test des icônes de statut
|
||||
- [ ] Test du tri et filtrage
|
||||
|
||||
#### 5. **Tests d'intégration** (`test/integration/`)
|
||||
|
||||
##### `test/integration/auth_flow_test.dart`
|
||||
- [ ] Test du flow complet de connexion
|
||||
- [ ] Test de persistance de session
|
||||
- [ ] Test de déconnexion
|
||||
- [ ] Test de redirection après expiration
|
||||
|
||||
##### `test/integration/membre_management_test.dart`
|
||||
- [ ] Test de création complète d'un membre
|
||||
- [ ] Test de modification avec validation
|
||||
- [ ] Test de suppression avec transfert
|
||||
- [ ] Test de gestion des erreurs
|
||||
|
||||
### 🛠️ Configuration des tests
|
||||
|
||||
#### Fichier `test/test_helpers.dart`
|
||||
```dart
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
// Mocks nécessaires
|
||||
class MockDio extends Mock implements Dio {}
|
||||
class MockBox<T> extends Mock implements Box<T> {}
|
||||
class MockApiService extends Mock implements ApiService {}
|
||||
|
||||
// Helper pour initialiser Hive en tests
|
||||
Future<void> setupTestHive() async {
|
||||
await Hive.initFlutter();
|
||||
// Initialiser les boîtes de test
|
||||
}
|
||||
|
||||
// Helper pour nettoyer après les tests
|
||||
Future<void> tearDownTestHive() async {
|
||||
await Hive.deleteFromDisk();
|
||||
}
|
||||
|
||||
// Helper pour créer des données de test
|
||||
class TestDataFactory {
|
||||
static UserModel createTestUser() {
|
||||
return UserModel(
|
||||
id: 1,
|
||||
username: 'test_user',
|
||||
email: 'test@example.com',
|
||||
// ...
|
||||
);
|
||||
}
|
||||
|
||||
static MembreModel createTestMembre() {
|
||||
return MembreModel(
|
||||
id: 1,
|
||||
fkEntite: 100,
|
||||
name: 'Test',
|
||||
firstName: 'User',
|
||||
// ...
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 📋 Commandes de test
|
||||
|
||||
```bash
|
||||
# Lancer tous les tests
|
||||
flutter test
|
||||
|
||||
# Lancer un test spécifique
|
||||
flutter test test/repositories/membre_repository_test.dart
|
||||
|
||||
# Lancer avec coverage
|
||||
flutter test --coverage
|
||||
|
||||
# Générer un rapport HTML de couverture
|
||||
genhtml coverage/lcov.info -o coverage/html
|
||||
open coverage/html/index.html
|
||||
```
|
||||
|
||||
### 🎯 Objectifs de couverture
|
||||
|
||||
- [ ] **Repositories** : 80% minimum
|
||||
- [ ] **Services** : 90% minimum
|
||||
- [ ] **Models** : 95% minimum
|
||||
- [ ] **Widgets critiques** : 70% minimum
|
||||
- [ ] **Couverture globale** : 75% minimum
|
||||
|
||||
### 📚 Dépendances de test à ajouter
|
||||
|
||||
Dans `pubspec.yaml` :
|
||||
```yaml
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
mockito: ^5.4.4
|
||||
build_runner: ^2.4.8
|
||||
test: ^1.25.2
|
||||
flutter_lints: ^4.0.0
|
||||
coverage: ^1.7.2
|
||||
```
|
||||
|
||||
### 🔄 Intégration CI/CD
|
||||
|
||||
Ajouter dans le pipeline CI :
|
||||
```yaml
|
||||
test:
|
||||
stage: test
|
||||
script:
|
||||
- flutter test --coverage
|
||||
- genhtml coverage/lcov.info -o coverage/html
|
||||
artifacts:
|
||||
paths:
|
||||
- coverage/
|
||||
coverage: '/lines\.*: \d+\.\d+\%/'
|
||||
```
|
||||
|
||||
### 📝 Bonnes pratiques
|
||||
|
||||
1. **Isolation** : Chaque test doit être indépendant
|
||||
2. **Mocking** : Utiliser des mocks pour les dépendances externes
|
||||
3. **Nommage** : Utiliser des noms descriptifs (test_should_xxx_when_yyy)
|
||||
4. **AAA** : Suivre le pattern Arrange-Act-Assert
|
||||
5. **Edge cases** : Tester les cas limites et erreurs
|
||||
6. **Performance** : Les tests unitaires doivent être rapides (<100ms)
|
||||
|
||||
---
|
||||
|
||||
## 💬 Module Chat en ligne GEOSECTOR
|
||||
|
||||
### 📋 Vue d'ensemble
|
||||
|
||||
Le module chat est partiellement implémenté avec une architecture MQTT pour les notifications en temps réel. Il nécessite des développements supplémentaires pour être pleinement fonctionnel.
|
||||
|
||||
### 🏗️ Architecture existante
|
||||
|
||||
```
|
||||
lib/chat/
|
||||
├── models/ # ✅ Modèles créés avec Hive
|
||||
│ ├── conversation_model.dart # Conversations (one-to-one, groupe, annonce)
|
||||
│ ├── message_model.dart # Messages avec pièces jointes
|
||||
│ ├── participant_model.dart # Participants aux conversations
|
||||
│ └── audience_target_model.dart # Cibles pour les annonces
|
||||
├── repositories/ # ⚠️ À compléter
|
||||
│ └── chat_repository.dart # Logique métier du chat
|
||||
├── services/ # ⚠️ Partiellement implémenté
|
||||
│ ├── chat_api_service.dart # Communication avec l'API
|
||||
│ ├── offline_queue_service.dart # File d'attente hors ligne
|
||||
│ └── notifications/ # 🔧 MQTT configuré
|
||||
│ ├── mqtt_notification_service.dart
|
||||
│ └── mqtt_config.dart
|
||||
├── widgets/ # ⚠️ À implémenter
|
||||
│ ├── chat_screen.dart # Écran principal du chat
|
||||
│ ├── conversations_list.dart # Liste des conversations
|
||||
│ ├── message_bubble.dart # Bulles de messages
|
||||
│ └── chat_input.dart # Zone de saisie
|
||||
└── pages/ # ⚠️ À compléter
|
||||
└── chat_page.dart # Page principale
|
||||
```
|
||||
|
||||
### 📝 Tâches de développement Chat
|
||||
|
||||
#### 1. **Finalisation des modèles Hive**
|
||||
- [ ] Compléter les adaptateurs Hive pour tous les modèles
|
||||
- [ ] Ajouter la gestion des pièces jointes (images, documents)
|
||||
- [ ] Implémenter le modèle `AnonymousUserModel` pour les utilisateurs temporaires
|
||||
- [ ] Ajouter les timestamps et statuts de lecture
|
||||
|
||||
#### 2. **Repository ChatRepository**
|
||||
- [ ] Implémenter `createConversation()` avec participants
|
||||
- [ ] Implémenter `sendMessage()` avec queue hors ligne
|
||||
- [ ] Implémenter `getConversations()` avec pagination
|
||||
- [ ] Implémenter `getMessages()` avec lazy loading
|
||||
- [ ] Ajouter la gestion des participants (ajout/suppression)
|
||||
- [ ] Implémenter les annonces ciblées par groupe
|
||||
|
||||
#### 3. **Services**
|
||||
- [ ] Compléter `ChatApiService` avec endpoints REST
|
||||
- [ ] Implémenter la synchronisation bidirectionnelle
|
||||
- [ ] Configurer `OfflineQueueService` pour messages en attente
|
||||
- [ ] Implémenter le retry automatique avec exponential backoff
|
||||
- [ ] Ajouter la compression des images avant envoi
|
||||
|
||||
#### 4. **Notifications MQTT**
|
||||
- [ ] Installer et configurer Mosquitto dans le container Incus
|
||||
- [ ] Configurer SSL/TLS pour MQTT (port 8883)
|
||||
- [ ] Implémenter l'authentification par token JWT
|
||||
- [ ] Créer les topics par utilisateur/groupe/conversation
|
||||
- [ ] Implémenter le système de présence (online/offline/typing)
|
||||
- [ ] Ajouter les ACLs pour sécuriser les topics
|
||||
|
||||
#### 5. **Interface utilisateur**
|
||||
- [ ] Créer `ChatScreen` avec liste de messages
|
||||
- [ ] Implémenter `ConversationsList` avec badges non-lus
|
||||
- [ ] Designer `MessageBubble` (texte, images, documents)
|
||||
- [ ] Créer `ChatInput` avec:
|
||||
- [ ] Saisie de texte avec emoji picker
|
||||
- [ ] Bouton d'envoi de fichiers
|
||||
- [ ] Indicateur "en train d'écrire"
|
||||
- [ ] Enregistrement vocal (optionnel)
|
||||
- [ ] Ajouter les animations (apparition messages, typing indicator)
|
||||
- [ ] Implémenter le swipe pour répondre
|
||||
- [ ] Ajouter la recherche dans les conversations
|
||||
|
||||
#### 6. **Fonctionnalités avancées**
|
||||
- [ ] Notifications push locales via `flutter_local_notifications`
|
||||
- [ ] Chiffrement end-to-end des messages sensibles
|
||||
- [ ] Réactions aux messages (emojis)
|
||||
- [ ] Messages éphémères avec auto-suppression
|
||||
- [ ] Partage de localisation en temps réel
|
||||
- [ ] Appels audio/vidéo via WebRTC (phase 2)
|
||||
|
||||
### 🔧 Configuration backend requise
|
||||
|
||||
#### Base de données
|
||||
```sql
|
||||
-- Tables à créer (voir chat_tables.sql)
|
||||
CREATE TABLE chat_conversations (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
type ENUM('one_to_one', 'group', 'announcement'),
|
||||
created_by INT,
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE chat_messages (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
conversation_id INT,
|
||||
sender_id INT,
|
||||
content TEXT,
|
||||
type ENUM('text', 'image', 'file', 'location'),
|
||||
status ENUM('sent', 'delivered', 'read'),
|
||||
created_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE chat_participants (
|
||||
conversation_id INT,
|
||||
user_id INT,
|
||||
role ENUM('admin', 'member'),
|
||||
joined_at TIMESTAMP,
|
||||
last_read_message_id INT
|
||||
);
|
||||
```
|
||||
|
||||
#### Configuration MQTT
|
||||
```bash
|
||||
# Installation Mosquitto
|
||||
apt-get install mosquitto mosquitto-clients
|
||||
|
||||
# Configuration /etc/mosquitto/mosquitto.conf
|
||||
listener 1883
|
||||
listener 8883
|
||||
cafile /etc/mosquitto/ca.crt
|
||||
certfile /etc/mosquitto/server.crt
|
||||
keyfile /etc/mosquitto/server.key
|
||||
allow_anonymous false
|
||||
password_file /etc/mosquitto/passwd
|
||||
```
|
||||
|
||||
### 📦 Dépendances à ajouter
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
# MQTT et notifications
|
||||
mqtt5_client: ^4.0.0
|
||||
flutter_local_notifications: ^17.0.0
|
||||
|
||||
# UI et UX
|
||||
emoji_picker_flutter: ^2.0.0
|
||||
cached_network_image: ^3.3.1
|
||||
photo_view: ^0.14.0
|
||||
file_picker: ^6.1.1
|
||||
|
||||
# Utilitaires
|
||||
path_provider: ^2.1.2
|
||||
image_picker: ^1.0.7
|
||||
image: ^4.1.7 # Pour compression
|
||||
intl: ^0.19.0 # Pour formatage dates
|
||||
```
|
||||
|
||||
### 🧪 Tests à implémenter
|
||||
- [ ] Tests unitaires des repositories
|
||||
- [ ] Tests d'intégration MQTT
|
||||
- [ ] Tests de performance (1000+ messages)
|
||||
- [ ] Tests hors ligne/online
|
||||
- [ ] Tests de sécurité (injection, XSS)
|
||||
|
||||
---
|
||||
|
||||
## 💳 Module de paiement Stripe
|
||||
|
||||
### 📋 Vue d'ensemble
|
||||
|
||||
Intégration de Stripe pour permettre aux amicales ayant activé `chk_stripe` d'accepter les paiements par carte bancaire avec une commission de 1.4%.
|
||||
|
||||
### 🎯 Objectifs
|
||||
|
||||
1. Permettre le paiement en ligne lors des passages
|
||||
2. Gérer les comptes Stripe des amicales
|
||||
3. Suivre les transactions et commissions
|
||||
4. Offrir une expérience de paiement fluide
|
||||
|
||||
### 📝 Tâches de développement Stripe
|
||||
|
||||
#### 1. **Configuration initiale Stripe**
|
||||
- [ ] Créer un compte Stripe Connect Platform
|
||||
- [ ] Configurer les webhooks Stripe
|
||||
- [ ] Mettre en place l'environnement de test (sandbox)
|
||||
- [ ] Configurer les clés API (publishable/secret)
|
||||
- [ ] Implémenter la gestion sécurisée des clés
|
||||
|
||||
#### 2. **Onboarding des amicales**
|
||||
- [ ] Créer un workflow d'inscription Stripe pour les amicales
|
||||
- [ ] Implémenter Stripe Connect Onboarding
|
||||
- [ ] Gérer le KYC (Know Your Customer) requis par Stripe
|
||||
- [ ] Stocker de manière sécurisée le `stripe_account_id`
|
||||
- [ ] Créer une page de statut du compte Stripe
|
||||
- [ ] Gérer les documents requis (RIB, statuts, etc.)
|
||||
|
||||
#### 3. **Modèles de données**
|
||||
- [ ] Créer `StripeAccountModel` pour les comptes Connect
|
||||
- [ ] Créer `PaymentIntentModel` pour les intentions de paiement
|
||||
- [ ] Créer `TransactionModel` pour l'historique
|
||||
- [ ] Ajouter les champs Stripe dans `PassageModel`
|
||||
- [ ] Implémenter la table des commissions
|
||||
|
||||
#### 4. **Service StripeService**
|
||||
```dart
|
||||
class StripeService {
|
||||
// Compte Connect
|
||||
Future<String> createConnectAccount(AmicaleModel amicale);
|
||||
Future<void> updateAccountStatus(String accountId);
|
||||
Future<String> createAccountLink(String accountId);
|
||||
|
||||
// Paiements
|
||||
Future<PaymentIntent> createPaymentIntent({
|
||||
required double amount,
|
||||
required String currency,
|
||||
required String connectedAccountId,
|
||||
});
|
||||
|
||||
Future<void> confirmPayment(String paymentIntentId);
|
||||
Future<void> refundPayment(String paymentIntentId);
|
||||
|
||||
// Commissions
|
||||
double calculateApplicationFee(double amount); // 1.4%
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. **Interface de paiement dans PassageForm**
|
||||
- [ ] Détecter si l'amicale accepte Stripe (`chk_stripe`)
|
||||
- [ ] Ajouter l'option "Paiement par carte" dans le dropdown
|
||||
- [ ] Intégrer Stripe Elements pour la saisie de carte
|
||||
- [ ] Implémenter le flow de paiement 3D Secure
|
||||
- [ ] Gérer les erreurs de paiement
|
||||
- [ ] Afficher le reçu de paiement
|
||||
- [ ] Permettre l'envoi du reçu par email
|
||||
|
||||
#### 6. **Widget StripePaymentSheet**
|
||||
```dart
|
||||
class StripePaymentSheet extends StatefulWidget {
|
||||
final double amount;
|
||||
final String currency;
|
||||
final AmicaleModel amicale;
|
||||
final Function(String) onSuccess;
|
||||
final Function(String) onError;
|
||||
|
||||
// UI avec:
|
||||
// - Montant à payer
|
||||
// - Formulaire de carte (Stripe Elements)
|
||||
// - Bouton de validation
|
||||
// - Indicateur de traitement
|
||||
// - Gestion 3D Secure
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. **Tableau de bord financier**
|
||||
- [ ] Page de suivi des transactions Stripe
|
||||
- [ ] Graphiques des paiements par période
|
||||
- [ ] Export des transactions (CSV/Excel)
|
||||
- [ ] Calcul automatique des commissions
|
||||
- [ ] Rapprochement bancaire
|
||||
- [ ] Dashboard temps réel des paiements
|
||||
|
||||
#### 8. **Webhooks Stripe**
|
||||
```php
|
||||
// Backend PHP pour gérer les webhooks
|
||||
class StripeWebhookHandler {
|
||||
// Events à gérer:
|
||||
- payment_intent.succeeded
|
||||
- payment_intent.failed
|
||||
- account.updated
|
||||
- payout.created
|
||||
- refund.created
|
||||
}
|
||||
```
|
||||
|
||||
#### 9. **Sécurité**
|
||||
- [ ] Chiffrement des données sensibles
|
||||
- [ ] Validation PCI DSS
|
||||
- [ ] Audit trail des transactions
|
||||
- [ ] Détection de fraude
|
||||
- [ ] Rate limiting sur les API
|
||||
- [ ] Tokenisation des cartes
|
||||
|
||||
#### 10. **Tests et conformité**
|
||||
- [ ] Tests avec cartes de test Stripe
|
||||
- [ ] Tests des cas d'erreur (carte refusée, etc.)
|
||||
- [ ] Tests 3D Secure
|
||||
- [ ] Tests de performance
|
||||
- [ ] Conformité RGPD pour les données de paiement
|
||||
- [ ] Documentation utilisateur
|
||||
|
||||
### 🔧 Configuration backend requise
|
||||
|
||||
#### Tables base de données
|
||||
```sql
|
||||
CREATE TABLE stripe_accounts (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
fk_entite INT NOT NULL,
|
||||
stripe_account_id VARCHAR(255) ENCRYPTED,
|
||||
status ENUM('pending', 'active', 'restricted'),
|
||||
charges_enabled BOOLEAN DEFAULT FALSE,
|
||||
payouts_enabled BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE stripe_transactions (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
fk_passage INT,
|
||||
stripe_payment_intent_id VARCHAR(255),
|
||||
amount DECIMAL(10,2),
|
||||
currency VARCHAR(3),
|
||||
status VARCHAR(50),
|
||||
application_fee DECIMAL(10,2),
|
||||
created_at TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
#### Variables d'environnement
|
||||
```env
|
||||
STRIPE_PUBLISHABLE_KEY=pk_test_xxx
|
||||
STRIPE_SECRET_KEY=sk_test_xxx
|
||||
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
||||
STRIPE_APPLICATION_FEE_PERCENT=1.4
|
||||
```
|
||||
|
||||
### 📦 Dépendances Stripe
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
# Stripe
|
||||
flutter_stripe: ^10.1.1
|
||||
|
||||
# Sécurité
|
||||
flutter_secure_storage: ^9.0.0
|
||||
|
||||
# UI
|
||||
shimmer: ^3.0.0 # Loading states
|
||||
lottie: ^3.1.0 # Animations succès/échec
|
||||
```
|
||||
|
||||
### 🚀 Roadmap d'implémentation
|
||||
|
||||
**Phase 1 (2 semaines)**
|
||||
- Configuration Stripe Connect
|
||||
- Onboarding basique des amicales
|
||||
- Tests en sandbox
|
||||
|
||||
**Phase 2 (3 semaines)**
|
||||
- Intégration dans PassageForm
|
||||
- Gestion des paiements simples
|
||||
- Webhooks essentiels
|
||||
|
||||
**Phase 3 (2 semaines)**
|
||||
- Dashboard financier
|
||||
- Export et rapports
|
||||
- Tests complets
|
||||
|
||||
**Phase 4 (1 semaine)**
|
||||
- Mise en production
|
||||
- Monitoring
|
||||
- Documentation
|
||||
|
||||
### 💰 Estimation des coûts
|
||||
|
||||
- **Stripe Connect** : 0.25€ par compte actif/mois
|
||||
- **Transactions** : 1.4% + 0.25€ par transaction
|
||||
- **Commission application** : 1.4% (reversée à GEOSECTOR)
|
||||
- **Coût net pour l'amicale** : ~2.8% + 0.25€ par transaction
|
||||
|
||||
---
|
||||
|
||||
**Date de création** : 2025-08-07
|
||||
**Auteur** : Architecture Team
|
||||
**Version** : 1.2.0
|
||||
@@ -60,6 +60,211 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
aFileChooser
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2011 - 2013 Paul Burke
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
abseil-cpp
|
||||
|
||||
@@ -919,6 +1124,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
--------------------------------------------------------------------------------
|
||||
accessibility
|
||||
engine
|
||||
image_picker
|
||||
spring_animation
|
||||
tonic
|
||||
url_launcher_web
|
||||
@@ -2952,6 +3158,72 @@ AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
--------------------------------------------------------------------------------
|
||||
cross_file
|
||||
file_selector_linux
|
||||
file_selector_macos
|
||||
file_selector_platform_interface
|
||||
file_selector_windows
|
||||
flutter_lints
|
||||
flutter_plugin_android_lifecycle
|
||||
go_router
|
||||
image_picker_for_web
|
||||
image_picker_linux
|
||||
image_picker_macos
|
||||
image_picker_platform_interface
|
||||
image_picker_windows
|
||||
path_provider
|
||||
path_provider_android
|
||||
path_provider_foundation
|
||||
path_provider_linux
|
||||
path_provider_platform_interface
|
||||
path_provider_windows
|
||||
plugin_platform_interface
|
||||
shared_preferences
|
||||
shared_preferences_android
|
||||
shared_preferences_foundation
|
||||
shared_preferences_linux
|
||||
shared_preferences_platform_interface
|
||||
shared_preferences_web
|
||||
shared_preferences_windows
|
||||
url_launcher
|
||||
url_launcher_android
|
||||
url_launcher_ios
|
||||
url_launcher_linux
|
||||
url_launcher_macos
|
||||
url_launcher_platform_interface
|
||||
url_launcher_windows
|
||||
vector_graphics
|
||||
vector_graphics_codec
|
||||
vector_graphics_compiler
|
||||
xdg_directories
|
||||
|
||||
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
cupertino_icons
|
||||
|
||||
@@ -6244,61 +6516,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
flutter_lints
|
||||
go_router
|
||||
path_provider
|
||||
path_provider_android
|
||||
path_provider_foundation
|
||||
path_provider_linux
|
||||
path_provider_platform_interface
|
||||
path_provider_windows
|
||||
plugin_platform_interface
|
||||
shared_preferences
|
||||
shared_preferences_android
|
||||
shared_preferences_foundation
|
||||
shared_preferences_linux
|
||||
shared_preferences_platform_interface
|
||||
shared_preferences_web
|
||||
shared_preferences_windows
|
||||
url_launcher
|
||||
url_launcher_android
|
||||
url_launcher_ios
|
||||
url_launcher_linux
|
||||
url_launcher_macos
|
||||
url_launcher_platform_interface
|
||||
url_launcher_windows
|
||||
vector_graphics
|
||||
vector_graphics_codec
|
||||
vector_graphics_compiler
|
||||
xdg_directories
|
||||
|
||||
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
flutter_local_notifications
|
||||
flutter_local_notifications_linux
|
||||
|
||||
Binary file not shown.
@@ -39,6 +39,6 @@ _flutter.buildConfig = {"engineRevision":"ef0cd000916d64fa0c5d09cc809fa7ad244a57
|
||||
|
||||
_flutter.loader.load({
|
||||
serviceWorkerSettings: {
|
||||
serviceWorkerVersion: "2510524361"
|
||||
serviceWorkerVersion: "2172432993"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ const RESOURCES = {"icons/Icon-maskable-192.png": "f36879dd176101fac324b68793e46
|
||||
"icons/Icon-512.png": "4495c4d7eeff38c1a967d16a8129bd2e",
|
||||
"icons/Icon-maskable-512.png": "4495c4d7eeff38c1a967d16a8129bd2e",
|
||||
"flutter.js": "83d881c1dbb6d6bcd6b42e274605b69c",
|
||||
"flutter_bootstrap.js": "c8b920ced05cd73436a61d1da7f46009",
|
||||
"flutter_bootstrap.js": "3b1d0de9e76904bfbfa6d48681275b2a",
|
||||
"favicon-64.png": "259540a3217e969237530444ca0eaed3",
|
||||
"index.html": "2aab03d10fea3b608e3eddc0fc0077e5",
|
||||
"/": "2aab03d10fea3b608e3eddc0fc0077e5",
|
||||
@@ -25,12 +25,12 @@ const RESOURCES = {"icons/Icon-maskable-192.png": "f36879dd176101fac324b68793e46
|
||||
"canvaskit/skwasm.js.symbols": "e72c79950c8a8483d826a7f0560573a1",
|
||||
"canvaskit/skwasm.js": "ea559890a088fe28b4ddf70e17e60052",
|
||||
"favicon-32.png": "21510778ead066ac826ad69302400773",
|
||||
"version.json": "2b834918f7d5565834c60f21a3a20768",
|
||||
"version.json": "843ab81033c65b1ade260713d5708244",
|
||||
"favicon.png": "21510778ead066ac826ad69302400773",
|
||||
"favicon-16.png": "106142fb24eba190e475dbe6513cc9ff",
|
||||
"assets/AssetManifest.json": "ee827821edbe97bd24fe72882535afca",
|
||||
"assets/AssetManifest.bin": "f1f694a4898aea6648eb53d349609844",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "a6c0d7cbaaafc86349d90ed45dca8016",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "78116d2cb29578b69c97b0f052fc3f88",
|
||||
"assets/FontManifest.json": "2eb88ea349cfc4d8628e771303d003ca",
|
||||
"assets/packages/flutter_map/lib/assets/flutter_map_logo.png": "208d63cc917af9713fc9572bd5c09362",
|
||||
"assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "33b7d9392238c04c131b6ce224e13711",
|
||||
@@ -44,9 +44,9 @@ const RESOURCES = {"icons/Icon-maskable-192.png": "f36879dd176101fac324b68793e46
|
||||
"assets/assets/fonts/Figtree-VariableFont_wght.ttf": "d25a5457a34fbf1c36b2937df1cf543b",
|
||||
"assets/assets/animations/geo_main.json": "e1c9755530d5f83718d4d43b0a36a703",
|
||||
"assets/shaders/ink_sparkle.frag": "ecc85a2e95f5e9f53123dcaf8cb9b6ce",
|
||||
"assets/NOTICES": "bbf2212d442929639b961f94116a2af7",
|
||||
"assets/NOTICES": "d1d24a6d37f88e05b7d4c2d8cc066528",
|
||||
"assets/AssetManifest.bin.json": "01a86053322475f2d9ce5c0a8d863d63",
|
||||
"main.dart.js": "9ec485d1d107e45a00e6761577a32bbf",
|
||||
"main.dart.js": "0f0789895f81dcd8d2a7e047d31ba345",
|
||||
"manifest.json": "4c436b37549165212484247d584e67cc"};
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
// start.
|
||||
|
||||
186081
app/build/web/main.dart.js
186081
app/build/web/main.dart.js
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"app_name":"geosector_app","version":"3.0.4","build_number":"304","package_name":"geosector_app"}
|
||||
{"app_name":"geosector_app","version":"3.0.6","build_number":"306","package_name":"geosector_app"}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:geosector_app/core/theme/app_theme.dart';
|
||||
import 'package:geosector_app/core/services/theme_service.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -44,6 +45,17 @@ class GeosectorApp extends StatelessWidget {
|
||||
themeMode: themeService.themeMode,
|
||||
routerConfig: _createRouter(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
// Configuration des localisations pour le français
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [
|
||||
Locale('fr', 'FR'), // Français comme langue principale
|
||||
Locale('en', 'US'), // Anglais en fallback
|
||||
],
|
||||
locale: const Locale('fr', 'FR'), // Forcer le français par défaut
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -70,6 +70,15 @@ class AmicaleModel extends HiveObject {
|
||||
@HiveField(21)
|
||||
final DateTime? updatedAt;
|
||||
|
||||
@HiveField(22)
|
||||
final bool chkMdpManuel;
|
||||
|
||||
@HiveField(23)
|
||||
final bool chkUsernameManuel;
|
||||
|
||||
@HiveField(24)
|
||||
final String? logoBase64; // Logo en base64 (data:image/png;base64,...)
|
||||
|
||||
AmicaleModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -93,6 +102,9 @@ class AmicaleModel extends HiveObject {
|
||||
this.chkStripe = false,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.chkMdpManuel = false,
|
||||
this.chkUsernameManuel = false,
|
||||
this.logoBase64,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
@@ -123,6 +135,17 @@ class AmicaleModel extends HiveObject {
|
||||
json['chk_active'] == 1 || json['chk_active'] == true;
|
||||
final bool chkStripe =
|
||||
json['chk_stripe'] == 1 || json['chk_stripe'] == true;
|
||||
final bool chkMdpManuel =
|
||||
json['chk_mdp_manuel'] == 1 || json['chk_mdp_manuel'] == true;
|
||||
final bool chkUsernameManuel =
|
||||
json['chk_username_manuel'] == 1 || json['chk_username_manuel'] == true;
|
||||
|
||||
// Traiter le logo si présent
|
||||
String? logoBase64;
|
||||
if (json['logo'] != null && json['logo'] is Map) {
|
||||
final logoData = json['logo'] as Map<String, dynamic>;
|
||||
logoBase64 = logoData['data_url'] as String?;
|
||||
}
|
||||
|
||||
// Traiter les dates si présentes
|
||||
DateTime? createdAt;
|
||||
@@ -166,6 +189,9 @@ class AmicaleModel extends HiveObject {
|
||||
chkStripe: chkStripe,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
chkMdpManuel: chkMdpManuel,
|
||||
chkUsernameManuel: chkUsernameManuel,
|
||||
logoBase64: logoBase64,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,6 +220,9 @@ class AmicaleModel extends HiveObject {
|
||||
'chk_stripe': chkStripe ? 1 : 0,
|
||||
'created_at': createdAt?.toIso8601String(),
|
||||
'updated_at': updatedAt?.toIso8601String(),
|
||||
'chk_mdp_manuel': chkMdpManuel ? 1 : 0,
|
||||
'chk_username_manuel': chkUsernameManuel ? 1 : 0,
|
||||
// Note: logoBase64 n'est pas envoyé via toJson (lecture seule depuis l'API)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -220,6 +249,9 @@ class AmicaleModel extends HiveObject {
|
||||
bool? chkStripe,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
bool? chkMdpManuel,
|
||||
bool? chkUsernameManuel,
|
||||
String? logoBase64,
|
||||
}) {
|
||||
return AmicaleModel(
|
||||
id: id,
|
||||
@@ -244,6 +276,9 @@ class AmicaleModel extends HiveObject {
|
||||
chkStripe: chkStripe ?? this.chkStripe,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
chkMdpManuel: chkMdpManuel ?? this.chkMdpManuel,
|
||||
chkUsernameManuel: chkUsernameManuel ?? this.chkUsernameManuel,
|
||||
logoBase64: logoBase64 ?? this.logoBase64,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,16 @@ class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
|
||||
chkStripe: fields[19] as bool,
|
||||
createdAt: fields[20] as DateTime?,
|
||||
updatedAt: fields[21] as DateTime?,
|
||||
chkMdpManuel: fields[22] as bool,
|
||||
chkUsernameManuel: fields[23] as bool,
|
||||
logoBase64: fields[24] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, AmicaleModel obj) {
|
||||
writer
|
||||
..writeByte(22)
|
||||
..writeByte(25)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
@@ -89,7 +92,13 @@ class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
|
||||
..writeByte(20)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(21)
|
||||
..write(obj.updatedAt);
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(22)
|
||||
..write(obj.chkMdpManuel)
|
||||
..writeByte(23)
|
||||
..write(obj.chkUsernameManuel)
|
||||
..writeByte(24)
|
||||
..write(obj.logoBase64);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -70,6 +70,12 @@ class ClientModel extends HiveObject {
|
||||
@HiveField(21)
|
||||
final DateTime? updatedAt;
|
||||
|
||||
@HiveField(22)
|
||||
final bool? chkMdpManuel;
|
||||
|
||||
@HiveField(23)
|
||||
final bool? chkUsernameManuel;
|
||||
|
||||
ClientModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -93,6 +99,8 @@ class ClientModel extends HiveObject {
|
||||
this.chkStripe,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.chkMdpManuel,
|
||||
this.chkUsernameManuel,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
@@ -138,6 +146,8 @@ class ClientModel extends HiveObject {
|
||||
chkStripe: json['chk_stripe'] == 1 || json['chk_stripe'] == true,
|
||||
createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null,
|
||||
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']) : null,
|
||||
chkMdpManuel: json['chk_mdp_manuel'] == 1 || json['chk_mdp_manuel'] == true,
|
||||
chkUsernameManuel: json['chk_username_manuel'] == 1 || json['chk_username_manuel'] == true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -166,6 +176,8 @@ class ClientModel extends HiveObject {
|
||||
'chk_stripe': chkStripe,
|
||||
'created_at': createdAt?.toIso8601String(),
|
||||
'updated_at': updatedAt?.toIso8601String(),
|
||||
'chk_mdp_manuel': chkMdpManuel,
|
||||
'chk_username_manuel': chkUsernameManuel,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -192,6 +204,8 @@ class ClientModel extends HiveObject {
|
||||
bool? chkStripe,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
bool? chkMdpManuel,
|
||||
bool? chkUsernameManuel,
|
||||
}) {
|
||||
return ClientModel(
|
||||
id: id,
|
||||
@@ -216,6 +230,8 @@ class ClientModel extends HiveObject {
|
||||
chkStripe: chkStripe ?? this.chkStripe,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
chkMdpManuel: chkMdpManuel ?? this.chkMdpManuel,
|
||||
chkUsernameManuel: chkUsernameManuel ?? this.chkUsernameManuel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,15 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
|
||||
chkStripe: fields[19] as bool?,
|
||||
createdAt: fields[20] as DateTime?,
|
||||
updatedAt: fields[21] as DateTime?,
|
||||
chkMdpManuel: fields[22] as bool?,
|
||||
chkUsernameManuel: fields[23] as bool?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ClientModel obj) {
|
||||
writer
|
||||
..writeByte(22)
|
||||
..writeByte(24)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
@@ -89,7 +91,11 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
|
||||
..writeByte(20)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(21)
|
||||
..write(obj.updatedAt);
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(22)
|
||||
..write(obj.chkMdpManuel)
|
||||
..writeByte(23)
|
||||
..write(obj.chkUsernameManuel);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/services/logger_service.dart';
|
||||
import 'package:geosector_app/core/utils/api_exception.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
|
||||
class MembreRepository extends ChangeNotifier {
|
||||
@@ -19,7 +21,7 @@ class MembreRepository extends ChangeNotifier {
|
||||
throw Exception('La boîte ${AppKeys.membresBoxName} n\'est pas ouverte. Initialisez d\'abord l\'application.');
|
||||
}
|
||||
_cachedMembreBox = Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||
debugPrint('MembreRepository: Box ${AppKeys.membresBoxName} mise en cache');
|
||||
LoggerService.database('MembreRepository: Box ${AppKeys.membresBoxName} mise en cache');
|
||||
}
|
||||
return _cachedMembreBox!;
|
||||
}
|
||||
@@ -120,7 +122,7 @@ class MembreRepository extends ChangeNotifier {
|
||||
// === MÉTHODES API ===
|
||||
|
||||
// Créer un membre via l'API
|
||||
Future<MembreModel?> createMembre(MembreModel membre) async {
|
||||
Future<MembreModel?> createMembre(MembreModel membre, {String? password}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
@@ -130,48 +132,97 @@ class MembreRepository extends ChangeNotifier {
|
||||
final data = userModel.toJson();
|
||||
data.remove('id'); // L'API génère l'ID
|
||||
data.remove('created_at'); // L'API génère created_at
|
||||
// Supprimer les champs de session qui ne doivent pas être envoyés
|
||||
data.remove('session_id');
|
||||
data.remove('session_expiry');
|
||||
data.remove('last_path');
|
||||
|
||||
// Convertir is_active en chk_active pour l'API
|
||||
if (data.containsKey('is_active')) {
|
||||
data['chk_active'] = data['is_active'] ? 1 : 0;
|
||||
data.remove('is_active');
|
||||
}
|
||||
|
||||
// Convertir role en fk_role pour l'API
|
||||
if (data.containsKey('role')) {
|
||||
data['fk_role'] = data['role'];
|
||||
data.remove('role');
|
||||
}
|
||||
|
||||
// Ajouter le mot de passe si fourni (sera ignoré par l'API si chk_mdp_manuel=0)
|
||||
if (password != null && password.isNotEmpty) {
|
||||
data['password'] = password;
|
||||
debugPrint('🔑 Mot de passe inclus dans la requête');
|
||||
} else {
|
||||
debugPrint('⚠️ Pas de mot de passe fourni');
|
||||
}
|
||||
|
||||
// Vérifier la présence de l'username (sera ignoré par l'API si chk_username_manuel=0)
|
||||
if (data.containsKey('username') && data['username'] != null && data['username'].toString().isNotEmpty) {
|
||||
debugPrint('👤 Username inclus dans la requête: ${data['username']}');
|
||||
} else {
|
||||
debugPrint('⚠️ Username manquant ou vide dans la requête');
|
||||
// Si pas d'username, s'assurer qu'il n'est pas envoyé du tout
|
||||
data.remove('username');
|
||||
}
|
||||
|
||||
LoggerService.api('Données envoyées à l\'API pour création membre: $data');
|
||||
|
||||
// Appeler l'API users
|
||||
final response = await ApiService.instance.post('/users', data: data);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
// Extraire l'ID de la réponse API
|
||||
final responseData = response.data;
|
||||
debugPrint('🎉 Réponse API création utilisateur: $responseData');
|
||||
// Vérifier d'abord si on a une réponse avec un statut d'erreur
|
||||
if (response.data != null && response.data is Map<String, dynamic>) {
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
|
||||
// L'API retourne {"status":"success","message":"Utilisateur créé avec succès","id":"10027748"}
|
||||
final userId = responseData['id'] is String ? int.parse(responseData['id']) : responseData['id'] as int;
|
||||
// Si l'API retourne un status error, propager le message
|
||||
if (responseData['status'] == 'error' && responseData['message'] != null) {
|
||||
throw Exception(responseData['message']);
|
||||
}
|
||||
|
||||
// Créer le nouveau membre avec l'ID retourné par l'API
|
||||
final createdMember = MembreModel(
|
||||
id: userId,
|
||||
fkEntite: membre.fkEntite,
|
||||
role: membre.role,
|
||||
fkTitre: membre.fkTitre,
|
||||
name: membre.name,
|
||||
firstName: membre.firstName,
|
||||
username: membre.username,
|
||||
sectName: membre.sectName,
|
||||
email: membre.email,
|
||||
phone: membre.phone,
|
||||
mobile: membre.mobile,
|
||||
dateNaissance: membre.dateNaissance,
|
||||
dateEmbauche: membre.dateEmbauche,
|
||||
createdAt: DateTime.now(),
|
||||
isActive: membre.isActive,
|
||||
);
|
||||
// Si succès avec code 201
|
||||
if (response.statusCode == 201 && responseData['status'] == 'success') {
|
||||
debugPrint('🎉 Réponse API création utilisateur: $responseData');
|
||||
|
||||
// Sauvegarder localement dans Hive (saveMembreBox gère déjà _resetCache)
|
||||
await saveMembreBox(createdMember);
|
||||
// L'API retourne {"status":"success","message":"Utilisateur créé avec succès","id":"10027748"}
|
||||
final userId = responseData['id'] is String ? int.parse(responseData['id']) : responseData['id'] as int;
|
||||
|
||||
debugPrint('✅ Membre créé avec l\'ID: $userId et sauvegardé localement');
|
||||
return createdMember;
|
||||
// Créer le nouveau membre avec l'ID retourné par l'API
|
||||
final createdMember = MembreModel(
|
||||
id: userId,
|
||||
fkEntite: membre.fkEntite,
|
||||
role: membre.role,
|
||||
fkTitre: membre.fkTitre,
|
||||
name: membre.name,
|
||||
firstName: membre.firstName,
|
||||
username: membre.username,
|
||||
sectName: membre.sectName,
|
||||
email: membre.email,
|
||||
phone: membre.phone,
|
||||
mobile: membre.mobile,
|
||||
dateNaissance: membre.dateNaissance,
|
||||
dateEmbauche: membre.dateEmbauche,
|
||||
createdAt: DateTime.now(),
|
||||
isActive: membre.isActive,
|
||||
);
|
||||
|
||||
// Sauvegarder localement dans Hive (saveMembreBox gère déjà _resetCache)
|
||||
await saveMembreBox(createdMember);
|
||||
|
||||
debugPrint('✅ Membre créé avec l\'ID: $userId et sauvegardé localement');
|
||||
return createdMember;
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('❌ Échec création membre - Code: ${response.statusCode}');
|
||||
return null;
|
||||
LoggerService.error('Échec création membre - Code: ${response.statusCode}');
|
||||
throw Exception('Erreur lors de la création du membre');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur lors de la création du membre: $e');
|
||||
// Ne pas logger les détails techniques de DioException
|
||||
if (e is ApiException) {
|
||||
LoggerService.error('Erreur lors de la création du membre: ${e.message}');
|
||||
} else {
|
||||
LoggerService.error('Erreur lors de la création du membre');
|
||||
}
|
||||
rethrow; // Propager l'exception pour la gestion d'erreurs
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
@@ -180,27 +231,67 @@ class MembreRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Mettre à jour un membre via l'API
|
||||
Future<bool> updateMembre(MembreModel membre) async {
|
||||
Future<bool> updateMembre(MembreModel membre, {String? password}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Convertir en UserModel pour l'API
|
||||
final userModel = membre.toUserModel();
|
||||
final data = userModel.toJson();
|
||||
// Supprimer les champs de session qui ne doivent pas être envoyés
|
||||
data.remove('session_id');
|
||||
data.remove('session_expiry');
|
||||
data.remove('last_path');
|
||||
|
||||
// Appeler l'API users au lieu de membres
|
||||
final response = await ApiService.instance.put('/users/${membre.id}', data: userModel.toJson());
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Sauvegarder le membre mis à jour localement
|
||||
await saveMembreBox(membre);
|
||||
return true;
|
||||
// Convertir is_active en chk_active pour l'API
|
||||
if (data.containsKey('is_active')) {
|
||||
data['chk_active'] = data['is_active'] ? 1 : 0;
|
||||
data.remove('is_active');
|
||||
}
|
||||
|
||||
return false;
|
||||
// Convertir role en fk_role pour l'API
|
||||
if (data.containsKey('role')) {
|
||||
data['fk_role'] = data['role'];
|
||||
data.remove('role');
|
||||
}
|
||||
|
||||
// Ajouter le mot de passe si fourni (sera ignoré par l'API si chk_mdp_manuel=0)
|
||||
if (password != null && password.isNotEmpty) {
|
||||
data['password'] = password;
|
||||
debugPrint('🔑 Mot de passe inclus dans la requête de mise à jour');
|
||||
} else {
|
||||
debugPrint('⚠️ Pas de mot de passe fourni pour la mise à jour');
|
||||
}
|
||||
|
||||
// L'username ne devrait pas être modifiable en update, mais on le garde pour l'API
|
||||
if (data.containsKey('username') && data['username'] != null && data['username'].toString().isNotEmpty) {
|
||||
debugPrint('👤 Username présent dans la requête de mise à jour: ${data['username']}');
|
||||
} else {
|
||||
debugPrint('⚠️ Username manquant dans la requête de mise à jour');
|
||||
}
|
||||
|
||||
LoggerService.api('Données envoyées à l\'API pour mise à jour membre: $data');
|
||||
|
||||
// Appeler l'API users au lieu de membres
|
||||
final response = await ApiService.instance.put('/users/${membre.id}', data: data);
|
||||
|
||||
// Si on arrive ici, c'est que la requête a réussi (200)
|
||||
// Sauvegarder le membre mis à jour localement
|
||||
await saveMembreBox(membre);
|
||||
return true;
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la mise à jour du membre: $e');
|
||||
return false;
|
||||
// Ne pas logger les détails techniques de DioException
|
||||
if (e is ApiException) {
|
||||
LoggerService.error('Erreur lors de la mise à jour du membre: ${e.message}');
|
||||
} else {
|
||||
LoggerService.error('Erreur lors de la mise à jour du membre');
|
||||
}
|
||||
|
||||
// Si c'est une DioException, elle sera automatiquement convertie en ApiException
|
||||
// par ApiException.fromDioException() qui extrait le message de l'API
|
||||
rethrow; // Propager l'exception pour que le message d'erreur soit affiché
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
@@ -215,13 +306,26 @@ class MembreRepository extends ChangeNotifier {
|
||||
try {
|
||||
final response = await ApiService.instance.post('/users/$membreId/reset-password');
|
||||
|
||||
// Vérifier si on a une réponse avec un statut d'erreur
|
||||
if (response.data != null && response.data is Map<String, dynamic>) {
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
if (responseData['status'] == 'error' && responseData['message'] != null) {
|
||||
throw Exception(responseData['message']);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
throw Exception('Erreur lors de la réinitialisation du mot de passe');
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la réinitialisation du mot de passe: $e');
|
||||
// Ne pas logger les détails techniques de DioException
|
||||
if (e is ApiException) {
|
||||
LoggerService.error('Erreur lors de la réinitialisation du mot de passe: ${e.message}');
|
||||
} else {
|
||||
LoggerService.error('Erreur lors de la réinitialisation du mot de passe');
|
||||
}
|
||||
rethrow;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
@@ -254,19 +358,32 @@ class MembreRepository extends ChangeNotifier {
|
||||
endpoint += '?${queryParams.join('&')}';
|
||||
}
|
||||
|
||||
debugPrint('🔗 DELETE endpoint: $endpoint');
|
||||
LoggerService.api('DELETE endpoint: $endpoint');
|
||||
|
||||
final response = await ApiService.instance.delete(endpoint);
|
||||
|
||||
// Vérifier si on a une réponse avec un statut d'erreur
|
||||
if (response.data != null && response.data is Map<String, dynamic>) {
|
||||
final responseData = response.data as Map<String, dynamic>;
|
||||
if (responseData['status'] == 'error' && responseData['message'] != null) {
|
||||
throw Exception(responseData['message']);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||
// Supprimer le membre localement
|
||||
await deleteMembreBox(membreId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
throw Exception('Erreur lors de la suppression du membre');
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la suppression du membre: $e');
|
||||
// Ne pas logger les détails techniques de DioException
|
||||
if (e is ApiException) {
|
||||
LoggerService.error('Erreur lors de la suppression du membre: ${e.message}');
|
||||
} else {
|
||||
LoggerService.error('Erreur lors de la suppression du membre');
|
||||
}
|
||||
rethrow;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
|
||||
@@ -15,12 +15,10 @@ import 'package:geosector_app/core/data/models/operation_model.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
import 'package:geosector_app/presentation/widgets/loading_progress_overlay.dart';
|
||||
import 'package:geosector_app/presentation/widgets/loading_spin_overlay.dart';
|
||||
import 'package:geosector_app/core/models/loading_state.dart';
|
||||
|
||||
class UserRepository extends ChangeNotifier {
|
||||
// Overlay pour afficher la progression du chargement
|
||||
OverlayEntry? _progressOverlay;
|
||||
bool _isLoading = false;
|
||||
|
||||
// Constructeur simplifié - plus d'injection d'ApiService
|
||||
@@ -336,64 +334,39 @@ class UserRepository extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Connexion avec interface utilisateur et progression
|
||||
Future<bool> loginWithUI(
|
||||
|
||||
/// Connexion avec spinner moderne (pour remplacer la barre de progression)
|
||||
Future<bool> loginWithSpinner(
|
||||
BuildContext context, String username, String password,
|
||||
{required String type}) async {
|
||||
OverlayEntry? spinOverlay;
|
||||
try {
|
||||
// Créer et afficher l'overlay de progression
|
||||
_progressOverlay = LoadingProgressOverlayUtils.show(
|
||||
// Déterminer le message selon le type de connexion
|
||||
final message = type == 'admin'
|
||||
? 'Connexion administrateur...'
|
||||
: 'Connexion utilisateur...';
|
||||
|
||||
// Afficher le spinner moderne
|
||||
spinOverlay = LoadingSpinOverlayUtils.show(
|
||||
context: context,
|
||||
message: 'Connexion en cours...',
|
||||
progress: 0.0,
|
||||
stepDescription: 'Préparation',
|
||||
blurAmount: 5.0,
|
||||
message: message,
|
||||
blurAmount: 10.0,
|
||||
showCard: true,
|
||||
);
|
||||
|
||||
// Écouter les changements d'état du DataLoadingService
|
||||
void listener() {
|
||||
if (_progressOverlay != null) {
|
||||
final loadingState = DataLoadingService.instance.loadingState;
|
||||
LoadingProgressOverlayUtils.update(
|
||||
overlayEntry: _progressOverlay!,
|
||||
message: loadingState.message,
|
||||
progress: loadingState.progress,
|
||||
stepDescription: loadingState.stepDescription,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Configurer le callback de progression
|
||||
DataLoadingService.instance.setProgressCallback((_) => listener());
|
||||
|
||||
// Exécuter la connexion
|
||||
final result = await login(username, password, type: type);
|
||||
|
||||
// Attendre un court instant pour que l'utilisateur voie le résultat
|
||||
if (result) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
} else {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
}
|
||||
// Petit délai pour une meilleure UX
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
|
||||
// Supprimer l'overlay
|
||||
if (_progressOverlay != null) {
|
||||
_progressOverlay!.remove();
|
||||
_progressOverlay = null;
|
||||
}
|
||||
|
||||
// Nettoyer le callback
|
||||
DataLoadingService.instance.setProgressCallback(null);
|
||||
// Fermer le spinner
|
||||
LoadingSpinOverlayUtils.hideSpecific(spinOverlay);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
// En cas d'erreur, supprimer l'overlay
|
||||
if (_progressOverlay != null) {
|
||||
_progressOverlay!.remove();
|
||||
_progressOverlay = null;
|
||||
}
|
||||
|
||||
DataLoadingService.instance.setProgressCallback(null);
|
||||
LoadingSpinOverlayUtils.hideSpecific(spinOverlay);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ class ApiService {
|
||||
late final String _appIdentifier;
|
||||
String? _sessionId;
|
||||
|
||||
// Getters pour les propriétés (lecture seule)
|
||||
String? get sessionId => _sessionId;
|
||||
String get baseUrl => _baseUrl;
|
||||
|
||||
// Singleton thread-safe
|
||||
static ApiService get instance {
|
||||
if (_instance == null) {
|
||||
@@ -142,8 +146,11 @@ class ApiService {
|
||||
Future<Response> post(String path, {dynamic data}) async {
|
||||
try {
|
||||
return await _dio.post(path, data: data);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête POST', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +158,11 @@ class ApiService {
|
||||
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
|
||||
try {
|
||||
return await _dio.get(path, queryParameters: queryParameters);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête GET', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,8 +170,11 @@ class ApiService {
|
||||
Future<Response> put(String path, {dynamic data}) async {
|
||||
try {
|
||||
return await _dio.put(path, data: data);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête PUT', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,8 +182,81 @@ class ApiService {
|
||||
Future<Response> delete(String path) async {
|
||||
try {
|
||||
return await _dio.delete(path);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête DELETE', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour uploader un logo d'amicale
|
||||
Future<Map<String, dynamic>> uploadLogo(int entiteId, dynamic imageFile) async {
|
||||
try {
|
||||
FormData formData;
|
||||
|
||||
// Gestion différente selon la plateforme (Web ou Mobile)
|
||||
if (kIsWeb) {
|
||||
// Pour le web, imageFile est un XFile
|
||||
final bytes = await imageFile.readAsBytes();
|
||||
|
||||
// Vérification de la taille (5 Mo max)
|
||||
const int maxSize = 5 * 1024 * 1024;
|
||||
if (bytes.length > maxSize) {
|
||||
throw ApiException(
|
||||
'Le fichier est trop volumineux. Taille maximale: 5 Mo',
|
||||
statusCode: 413
|
||||
);
|
||||
}
|
||||
|
||||
formData = FormData.fromMap({
|
||||
'logo': MultipartFile.fromBytes(
|
||||
bytes,
|
||||
filename: imageFile.name,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
// Pour mobile, imageFile est un File
|
||||
final fileLength = await imageFile.length();
|
||||
|
||||
// Vérification de la taille (5 Mo max)
|
||||
const int maxSize = 5 * 1024 * 1024;
|
||||
if (fileLength > maxSize) {
|
||||
throw ApiException(
|
||||
'Le fichier est trop volumineux. Taille maximale: 5 Mo',
|
||||
statusCode: 413
|
||||
);
|
||||
}
|
||||
|
||||
formData = FormData.fromMap({
|
||||
'logo': await MultipartFile.fromFile(
|
||||
imageFile.path,
|
||||
filename: imageFile.path.split('/').last,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
final response = await _dio.post(
|
||||
'/entites/$entiteId/logo',
|
||||
data: formData,
|
||||
options: Options(
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return response.data;
|
||||
} else {
|
||||
throw ApiException('Erreur lors de l\'upload du logo',
|
||||
statusCode: response.statusCode);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de l\'upload du logo', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
163
app/lib/core/services/logger_service.dart
Normal file
163
app/lib/core/services/logger_service.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
|
||||
/// Service de logging centralisé qui désactive automatiquement les logs en production
|
||||
class LoggerService {
|
||||
static LoggerService? _instance;
|
||||
static bool? _isProduction;
|
||||
|
||||
// Singleton
|
||||
static LoggerService get instance {
|
||||
_instance ??= LoggerService._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
LoggerService._internal();
|
||||
|
||||
/// Détermine si on est en production
|
||||
static bool get isProduction {
|
||||
if (_isProduction != null) return _isProduction!;
|
||||
|
||||
try {
|
||||
// Vérifier si ApiService est initialisé
|
||||
final env = ApiService.instance.getCurrentEnvironment();
|
||||
_isProduction = env == 'PROD';
|
||||
} catch (e) {
|
||||
// Si ApiService n'est pas encore initialisé, utiliser kReleaseMode
|
||||
_isProduction = kReleaseMode;
|
||||
}
|
||||
|
||||
return _isProduction!;
|
||||
}
|
||||
|
||||
/// Réinitialiser le cache de l'environnement (utile pour les tests)
|
||||
static void resetEnvironmentCache() {
|
||||
_isProduction = null;
|
||||
}
|
||||
|
||||
/// Log simple (remplace debugPrint)
|
||||
static void log(String message, {String? prefix}) {
|
||||
if (!isProduction) {
|
||||
if (prefix != null) {
|
||||
debugPrint('$prefix $message');
|
||||
} else {
|
||||
debugPrint(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Log d'information
|
||||
static void info(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('ℹ️ $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de succès
|
||||
static void success(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('✅ $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log d'avertissement
|
||||
static void warning(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('⚠️ $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log d'erreur (toujours affiché même en production pour le debugging)
|
||||
static void error(String message, [dynamic error, StackTrace? stackTrace]) {
|
||||
// Les erreurs sont toujours loggées, même en production
|
||||
debugPrint('❌ $message');
|
||||
if (error != null) {
|
||||
debugPrint(' Error: $error');
|
||||
}
|
||||
if (stackTrace != null && !isProduction) {
|
||||
// La stack trace n'est affichée qu'en DEV/REC
|
||||
debugPrint(' Stack trace:\n$stackTrace');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de debug (avec emoji personnalisé)
|
||||
static void debug(String message, {String emoji = '🔧'}) {
|
||||
if (!isProduction) {
|
||||
debugPrint('$emoji $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de requête API
|
||||
static void api(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('🔗 $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de base de données
|
||||
static void database(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('💾 $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de navigation
|
||||
static void navigation(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('🧭 $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de performance/timing
|
||||
static void performance(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('⏱️ $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log conditionnel basé sur un flag custom
|
||||
static void conditional(String message, {required bool condition}) {
|
||||
if (!isProduction && condition) {
|
||||
debugPrint(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Groupe de logs (pour regrouper visuellement des logs liés)
|
||||
static void group(String title, List<String> messages) {
|
||||
if (!isProduction) {
|
||||
debugPrint('┌─ $title');
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
if (i == messages.length - 1) {
|
||||
debugPrint('└─ ${messages[i]}');
|
||||
} else {
|
||||
debugPrint('├─ ${messages[i]}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Log d'objet JSON (formaté)
|
||||
static void json(String label, Map<String, dynamic> data) {
|
||||
if (!isProduction) {
|
||||
debugPrint('📋 $label:');
|
||||
data.forEach((key, value) {
|
||||
debugPrint(' $key: $value');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension pour faciliter l'utilisation
|
||||
extension LoggerExtension on String {
|
||||
void log({String? prefix}) => LoggerService.log(this, prefix: prefix);
|
||||
void logInfo() => LoggerService.info(this);
|
||||
void logSuccess() => LoggerService.success(this);
|
||||
void logWarning() => LoggerService.warning(this);
|
||||
void logError([dynamic error, StackTrace? stackTrace]) =>
|
||||
LoggerService.error(this, error, stackTrace);
|
||||
void logDebug({String emoji = '🔧'}) => LoggerService.debug(this, emoji: emoji);
|
||||
void logApi() => LoggerService.api(this);
|
||||
void logDatabase() => LoggerService.database(this);
|
||||
void logNavigation() => LoggerService.navigation(this);
|
||||
void logPerformance() => LoggerService.performance(this);
|
||||
}
|
||||
@@ -97,6 +97,9 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||
}
|
||||
|
||||
void _handleEditMembre(MembreModel membre) {
|
||||
// Récupérer l'amicale actuelle
|
||||
final amicale = widget.amicaleRepository.getUserAmicale(_currentUser!.fkEntite!);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => UserFormDialog(
|
||||
@@ -104,8 +107,9 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||
user: membre.toUserModel(),
|
||||
showRoleSelector: true,
|
||||
showActiveCheckbox: true, // Activer la checkbox
|
||||
allowUsernameEdit: true, // Permettre l'édition du username
|
||||
// allowSectNameEdit sera automatiquement true via UserForm
|
||||
allowUsernameEdit: amicale?.chkUsernameManuel == true, // Conditionnel selon amicale
|
||||
amicale: amicale, // Passer l'amicale
|
||||
isAdmin: true, // Car on est dans la page admin
|
||||
availableRoles: const [
|
||||
RoleOption(
|
||||
value: 1,
|
||||
@@ -118,23 +122,22 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||
description: 'Peut gérer l\'amicale et ses membres',
|
||||
),
|
||||
],
|
||||
onSubmit: (updatedUser) async {
|
||||
onSubmit: (updatedUser, {String? password}) async {
|
||||
try {
|
||||
// Convertir le UserModel mis à jour vers MembreModel
|
||||
final updatedMembre =
|
||||
MembreModel.fromUserModel(updatedUser, membre);
|
||||
|
||||
// Utiliser directement updateMembre qui passe par l'API /users
|
||||
final success =
|
||||
await widget.membreRepository.updateMembre(updatedMembre);
|
||||
final success = await widget.membreRepository.updateMembre(
|
||||
updatedMembre,
|
||||
password: password,
|
||||
);
|
||||
|
||||
if (success && mounted) {
|
||||
Navigator.of(context).pop();
|
||||
ApiException.showSuccess(context,
|
||||
'Membre ${updatedMembre.firstName} ${updatedMembre.name} mis à jour');
|
||||
} else if (!success && mounted) {
|
||||
ApiException.showError(
|
||||
context, Exception('Erreur lors de la mise à jour'));
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur mise à jour membre: $e');
|
||||
@@ -557,9 +560,6 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||
if (success && mounted) {
|
||||
ApiException.showSuccess(context,
|
||||
'Membre ${membre.firstName} ${membre.name} désactivé avec succès');
|
||||
} else if (mounted) {
|
||||
ApiException.showError(
|
||||
context, Exception('Erreur lors de la désactivation'));
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
@@ -571,6 +571,9 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||
void _handleAddMembre() {
|
||||
if (_currentUser?.fkEntite == null) return;
|
||||
|
||||
// Récupérer l'amicale actuelle
|
||||
final amicale = widget.amicaleRepository.getUserAmicale(_currentUser!.fkEntite!);
|
||||
|
||||
// Créer un UserModel vide avec les valeurs par défaut
|
||||
final newUser = UserModel(
|
||||
id: 0, // ID temporaire pour nouveau membre
|
||||
@@ -596,7 +599,9 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||
user: newUser,
|
||||
showRoleSelector: true,
|
||||
showActiveCheckbox: true,
|
||||
allowUsernameEdit: true,
|
||||
allowUsernameEdit: amicale?.chkUsernameManuel == true, // Conditionnel selon amicale
|
||||
amicale: amicale, // Passer l'amicale
|
||||
isAdmin: true, // Car on est dans la page admin
|
||||
availableRoles: const [
|
||||
RoleOption(
|
||||
value: 1,
|
||||
@@ -609,7 +614,7 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||
description: 'Peut gérer l\'amicale et ses membres',
|
||||
),
|
||||
],
|
||||
onSubmit: (newUserData) async {
|
||||
onSubmit: (newUserData, {String? password}) async {
|
||||
try {
|
||||
// Créer un nouveau MembreModel directement
|
||||
final newMembre = MembreModel(
|
||||
@@ -631,8 +636,10 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||
);
|
||||
|
||||
// Créer le membre via l'API (retourne maintenant le membre créé)
|
||||
final createdMembre =
|
||||
await widget.membreRepository.createMembre(newMembre);
|
||||
final createdMembre = await widget.membreRepository.createMembre(
|
||||
newMembre,
|
||||
password: password,
|
||||
);
|
||||
|
||||
if (createdMembre != null && mounted) {
|
||||
// Fermer le dialog
|
||||
|
||||
@@ -434,7 +434,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
'Login: Tentative avec type: $_loginType');
|
||||
|
||||
final success =
|
||||
await userRepository.login(
|
||||
await userRepository.loginWithSpinner(
|
||||
context,
|
||||
_usernameController.text.trim(),
|
||||
_passwordController.text,
|
||||
type: _loginType,
|
||||
@@ -575,9 +576,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||
print(
|
||||
'Login: Tentative avec type: $_loginType');
|
||||
|
||||
// Utiliser directement userRepository avec l'overlay de chargement
|
||||
// Utiliser le nouveau spinner moderne pour la connexion
|
||||
final success = await userRepository
|
||||
.loginWithUI(
|
||||
.loginWithSpinner(
|
||||
context,
|
||||
_usernameController.text.trim(),
|
||||
_passwordController.text,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/presentation/widgets/mapbox_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'dart:io';
|
||||
import 'package:geosector_app/presentation/widgets/loading_spin_overlay.dart';
|
||||
import 'custom_text_field.dart';
|
||||
|
||||
class AmicaleForm extends StatefulWidget {
|
||||
@@ -52,6 +58,13 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
bool _chkAcceptSms = false;
|
||||
bool _chkActive = true;
|
||||
bool _chkStripe = false;
|
||||
bool _chkMdpManuel = false;
|
||||
bool _chkUsernameManuel = false;
|
||||
|
||||
// Pour l'upload du logo
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
XFile? _selectedImage;
|
||||
String? _logoUrl;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -78,6 +91,10 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
_chkAcceptSms = amicale?.chkAcceptSms ?? false;
|
||||
_chkActive = amicale?.chkActive ?? true;
|
||||
_chkStripe = amicale?.chkStripe ?? false;
|
||||
_chkMdpManuel = amicale?.chkMdpManuel ?? false;
|
||||
_chkUsernameManuel = amicale?.chkUsernameManuel ?? false;
|
||||
|
||||
// Note : Le logo sera chargé dynamiquement depuis l'API
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -133,6 +150,8 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
'chk_copie_mail_recu': amicale.chkCopieMailRecu ? 1 : 0,
|
||||
'chk_accept_sms': amicale.chkAcceptSms ? 1 : 0,
|
||||
'chk_stripe': amicale.chkStripe ? 1 : 0,
|
||||
'chk_mdp_manuel': amicale.chkMdpManuel ? 1 : 0,
|
||||
'chk_username_manuel': amicale.chkUsernameManuel ? 1 : 0,
|
||||
};
|
||||
|
||||
// Ajouter les champs réservés aux administrateurs si l'utilisateur est admin
|
||||
@@ -232,6 +251,115 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour sélectionner une image
|
||||
Future<void> _selectImage() async {
|
||||
try {
|
||||
final XFile? image = await _picker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
maxWidth: 1024,
|
||||
maxHeight: 1024,
|
||||
imageQuality: 85,
|
||||
);
|
||||
|
||||
if (image != null) {
|
||||
// Vérifier la taille du fichier (limite 5 Mo)
|
||||
final int fileSize = await image.length();
|
||||
const int maxSize = 5 * 1024 * 1024; // 5 Mo en octets
|
||||
|
||||
if (fileSize > maxSize) {
|
||||
// Fichier trop volumineux
|
||||
final double sizeMB = fileSize / (1024 * 1024);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Le fichier est trop volumineux (${sizeMB.toStringAsFixed(2)} Mo). '
|
||||
'La taille maximale autorisée est de 5 Mo.',
|
||||
),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_selectedImage = image;
|
||||
});
|
||||
|
||||
// Upload immédiatement après sélection
|
||||
if (widget.amicale?.id != null) {
|
||||
await _uploadLogo();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la sélection de l\'image: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur lors de la sélection de l\'image: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour uploader le logo
|
||||
Future<void> _uploadLogo() async {
|
||||
if (_selectedImage == null || widget.amicale?.id == null) return;
|
||||
|
||||
OverlayEntry? spinOverlay;
|
||||
try {
|
||||
// Afficher le spinner
|
||||
spinOverlay = LoadingSpinOverlayUtils.show(
|
||||
context: context,
|
||||
message: 'Upload du logo en cours...',
|
||||
blurAmount: 10.0,
|
||||
showCard: true,
|
||||
);
|
||||
|
||||
// Appeler l'API pour uploader le logo
|
||||
final response = await widget.apiService?.uploadLogo(
|
||||
widget.amicale!.id,
|
||||
_selectedImage!,
|
||||
);
|
||||
|
||||
if (response != null && response['status'] == 'success') {
|
||||
// Succès
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Logo uploadé avec succès'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Mettre à jour l'amicale avec le nouveau logo en base64
|
||||
// Note : Le serveur devrait aussi mettre à jour le logo dans la session
|
||||
// Pour l'instant on garde l'image sélectionnée en preview
|
||||
setState(() {
|
||||
// L'image reste en preview jusqu'au prochain login
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'upload du logo: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur lors de l\'upload: ${e.toString()}'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
// Fermer le spinner
|
||||
LoadingSpinOverlayUtils.hideSpecific(spinOverlay);
|
||||
}
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
debugPrint('🔧 _submitForm appelée');
|
||||
|
||||
@@ -271,6 +399,8 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
chkAcceptSms: _chkAcceptSms,
|
||||
chkActive: _chkActive,
|
||||
chkStripe: _chkStripe,
|
||||
chkMdpManuel: _chkMdpManuel,
|
||||
chkUsernameManuel: _chkUsernameManuel,
|
||||
) ??
|
||||
AmicaleModel(
|
||||
id: 0, // Sera remplacé par l'API
|
||||
@@ -291,6 +421,9 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
chkCopieMailRecu: _chkCopieMailRecu,
|
||||
chkAcceptSms: _chkAcceptSms,
|
||||
chkActive: _chkActive,
|
||||
chkStripe: _chkStripe,
|
||||
chkMdpManuel: _chkMdpManuel,
|
||||
chkUsernameManuel: _chkUsernameManuel,
|
||||
);
|
||||
|
||||
debugPrint('🔧 AmicaleModel créé: ${amicale.name}');
|
||||
@@ -305,6 +438,10 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
|
||||
// Construire la section logo
|
||||
Widget _buildLogoSection() {
|
||||
// Vérifier si on est admin d'amicale (role 2)
|
||||
final userRole = widget.userRepository.getUserRole();
|
||||
final canUploadLogo = userRole == 2 && !widget.readOnly;
|
||||
|
||||
return Container(
|
||||
width: 150,
|
||||
height: 150,
|
||||
@@ -323,40 +460,40 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Image par défaut
|
||||
// Afficher l'image sélectionnée, ou le logo depuis l'API, ou l'image par défaut
|
||||
Center(
|
||||
child: Image.asset(
|
||||
'assets/images/logo_recu.png',
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
child: _buildLogoImage(),
|
||||
),
|
||||
|
||||
// Overlay pour indiquer que l'image est modifiable (si non en lecture seule)
|
||||
if (!widget.readOnly)
|
||||
// Overlay pour indiquer que l'image est modifiable (si admin d'amicale)
|
||||
if (canUploadLogo)
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// TODO: Implémenter la sélection d'image
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Fonctionnalité de modification du logo à venir'),
|
||||
),
|
||||
);
|
||||
},
|
||||
onTap: _selectImage,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.camera_alt,
|
||||
color: Colors.white,
|
||||
size: 40,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.camera_alt,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'Modifier',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -368,6 +505,102 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
);
|
||||
}
|
||||
|
||||
// Méthode pour construire l'image du logo
|
||||
Widget _buildLogoImage() {
|
||||
// 1. Si une image a été sélectionnée localement (preview)
|
||||
if (_selectedImage != null) {
|
||||
if (kIsWeb) {
|
||||
return FutureBuilder<Uint8List>(
|
||||
future: _selectedImage!.readAsBytes(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Image.memory(
|
||||
snapshot.data!,
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
}
|
||||
return const CircularProgressIndicator();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Image.file(
|
||||
File(_selectedImage!.path),
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Si l'amicale a un logo en base64 stocké dans Hive
|
||||
if (widget.amicale?.logoBase64 != null && widget.amicale!.logoBase64!.isNotEmpty) {
|
||||
try {
|
||||
// Le logoBase64 contient déjà le data URL complet (data:image/png;base64,...)
|
||||
final dataUrl = widget.amicale!.logoBase64!;
|
||||
|
||||
// Extraire le base64 du data URL
|
||||
final base64Data = dataUrl.split(',').last;
|
||||
final bytes = base64Decode(base64Data);
|
||||
|
||||
return Image.memory(
|
||||
bytes,
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
debugPrint('Erreur affichage logo base64: $error');
|
||||
// En cas d'erreur, essayer l'API
|
||||
return _buildLogoFromApi();
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur décodage base64: $e');
|
||||
// En cas d'erreur, essayer l'API
|
||||
return _buildLogoFromApi();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Sinon, essayer de charger depuis l'API
|
||||
return _buildLogoFromApi();
|
||||
}
|
||||
|
||||
// Méthode pour charger le logo depuis l'API
|
||||
Widget _buildLogoFromApi() {
|
||||
if (widget.amicale?.id != null && widget.apiService != null) {
|
||||
// Construire l'URL complète du logo
|
||||
final logoUrl = '${widget.apiService!.baseUrl}/entites/${widget.amicale!.id}/logo';
|
||||
|
||||
return Image.network(
|
||||
logoUrl,
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
headers: {
|
||||
'Authorization': 'Bearer ${widget.apiService!.sessionId ?? ""}',
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
// En cas d'erreur, afficher l'image par défaut
|
||||
return Image.asset(
|
||||
'assets/images/logo_recu.png',
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Par défaut, afficher l'image locale
|
||||
return Image.asset(
|
||||
'assets/images/logo_recu.png',
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
}
|
||||
|
||||
// Construire la minimap
|
||||
Widget _buildMiniMap() {
|
||||
// Vérifier si les coordonnées GPS sont valides
|
||||
@@ -825,59 +1058,106 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Checkbox Demo
|
||||
_buildCheckboxOption(
|
||||
label: "Mode démo",
|
||||
value: _chkDemo,
|
||||
onChanged: restrictedFieldsReadOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkDemo = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Options organisées sur 2 colonnes
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Colonne de gauche
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
// Checkbox Demo
|
||||
_buildCheckboxOption(
|
||||
label: "Mode démo",
|
||||
value: _chkDemo,
|
||||
onChanged: restrictedFieldsReadOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkDemo = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Checkbox Copie Mail Reçu
|
||||
_buildCheckboxOption(
|
||||
label: "Copie des mails reçus",
|
||||
value: _chkCopieMailRecu,
|
||||
onChanged: widget.readOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkCopieMailRecu = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Checkbox Copie Mail Reçu
|
||||
_buildCheckboxOption(
|
||||
label: "Copie des mails reçus",
|
||||
value: _chkCopieMailRecu,
|
||||
onChanged: widget.readOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkCopieMailRecu = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Checkbox Accept SMS
|
||||
_buildCheckboxOption(
|
||||
label: "Accepte les SMS",
|
||||
value: _chkAcceptSms,
|
||||
onChanged: widget.readOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkAcceptSms = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Checkbox Accept SMS
|
||||
_buildCheckboxOption(
|
||||
label: "Accepte les SMS",
|
||||
value: _chkAcceptSms,
|
||||
onChanged: widget.readOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkAcceptSms = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 32),
|
||||
// Colonne de droite
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
// Checkbox Active
|
||||
_buildCheckboxOption(
|
||||
label: "Actif",
|
||||
value: _chkActive,
|
||||
onChanged: restrictedFieldsReadOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkActive = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Checkbox Active
|
||||
_buildCheckboxOption(
|
||||
label: "Actif",
|
||||
value: _chkActive,
|
||||
onChanged: restrictedFieldsReadOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkActive = value!;
|
||||
});
|
||||
},
|
||||
// Checkbox Mot de passe manuel
|
||||
_buildCheckboxOption(
|
||||
label: "Saisie manuelle des mots de passe",
|
||||
value: _chkMdpManuel,
|
||||
onChanged: widget.readOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkMdpManuel = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Checkbox Username manuel
|
||||
_buildCheckboxOption(
|
||||
label: "Saisie manuelle des identifiants",
|
||||
value: _chkUsernameManuel,
|
||||
onChanged: widget.readOnly
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_chkUsernameManuel = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
|
||||
|
||||
@@ -171,9 +171,10 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
user: user,
|
||||
readOnly: false,
|
||||
showRoleSelector: false,
|
||||
onSubmit: (updatedUser) async {
|
||||
onSubmit: (updatedUser, {String? password}) async {
|
||||
try {
|
||||
// Sauvegarder les modifications de l'utilisateur
|
||||
// Note: password est ignoré ici car l'utilisateur normal ne peut pas changer son mot de passe
|
||||
await userRepository.updateUser(updatedUser);
|
||||
|
||||
if (context.mounted) {
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:ui';
|
||||
|
||||
/// Widget d'overlay de chargement amélioré qui affiche une barre de progression
|
||||
/// avec un effet de flou sur l'arrière-plan et un message détaillé sur l'étape en cours
|
||||
class LoadingProgressOverlay extends StatefulWidget {
|
||||
final String? message;
|
||||
final double progress;
|
||||
final String? stepDescription;
|
||||
final Color backgroundColor;
|
||||
final Color progressColor;
|
||||
final Color textColor;
|
||||
final double blurAmount;
|
||||
final bool showPercentage;
|
||||
|
||||
const LoadingProgressOverlay({
|
||||
super.key,
|
||||
this.message,
|
||||
required this.progress,
|
||||
this.stepDescription,
|
||||
this.backgroundColor = Colors.black54,
|
||||
this.progressColor = Colors.white,
|
||||
this.textColor = Colors.white,
|
||||
this.blurAmount = 5.0,
|
||||
this.showPercentage = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LoadingProgressOverlay> createState() => _LoadingProgressOverlayState();
|
||||
}
|
||||
|
||||
class _LoadingProgressOverlayState extends State<LoadingProgressOverlay>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _progressAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
_progressAnimation = Tween<double>(begin: 0, end: widget.progress).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
||||
);
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(LoadingProgressOverlay oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.progress != widget.progress) {
|
||||
_progressAnimation = Tween<double>(
|
||||
begin: oldWidget.progress,
|
||||
end: widget.progress,
|
||||
).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
||||
);
|
||||
_animationController.reset();
|
||||
_animationController.forward();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: widget.blurAmount, sigmaY: widget.blurAmount),
|
||||
child: Container(
|
||||
color: widget.backgroundColor,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.85,
|
||||
padding: const EdgeInsets.all(30),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black45,
|
||||
blurRadius: 15,
|
||||
spreadRadius: 5,
|
||||
offset: Offset(0, 5),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.message != null) ...[
|
||||
Text(
|
||||
widget.message!,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: widget.textColor,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
AnimatedBuilder(
|
||||
animation: _progressAnimation,
|
||||
builder: (context, child) {
|
||||
return Column(
|
||||
children: [
|
||||
LinearProgressIndicator(
|
||||
value: _progressAnimation.value,
|
||||
backgroundColor:
|
||||
widget.progressColor.withOpacity(0.3),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
widget.progressColor),
|
||||
minHeight: 15,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
if (widget.showPercentage) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${(_progressAnimation.value * 100).toInt()}%',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: widget.textColor,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (widget.stepDescription != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
widget.stepDescription!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: widget.textColor.withOpacity(0.9),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Classe utilitaire pour gérer l'overlay de chargement avec progression
|
||||
class LoadingProgressOverlayUtils {
|
||||
/// Méthode pour afficher l'overlay de chargement avec progression
|
||||
static OverlayEntry show({
|
||||
required BuildContext context,
|
||||
String? message,
|
||||
double progress = 0.0,
|
||||
String? stepDescription,
|
||||
double blurAmount = 5.0,
|
||||
bool showPercentage = true,
|
||||
}) {
|
||||
final overlayEntry = OverlayEntry(
|
||||
builder: (context) => LoadingProgressOverlay(
|
||||
message: message,
|
||||
progress: progress,
|
||||
stepDescription: stepDescription,
|
||||
blurAmount: blurAmount,
|
||||
showPercentage: showPercentage,
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.of(context).insert(overlayEntry);
|
||||
return overlayEntry;
|
||||
}
|
||||
|
||||
/// Méthode pour mettre à jour l'overlay existant
|
||||
static void update({
|
||||
required OverlayEntry overlayEntry,
|
||||
String? message,
|
||||
required double progress,
|
||||
String? stepDescription,
|
||||
}) {
|
||||
overlayEntry.markNeedsBuild();
|
||||
}
|
||||
}
|
||||
229
app/lib/presentation/widgets/loading_spin_overlay.dart
Normal file
229
app/lib/presentation/widgets/loading_spin_overlay.dart
Normal file
@@ -0,0 +1,229 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:ui';
|
||||
|
||||
/// Widget d'overlay de chargement moderne avec spinner circulaire
|
||||
/// Affiche un spinner animé avec fond flou et message optionnel
|
||||
class LoadingSpinOverlay extends StatefulWidget {
|
||||
final String? message;
|
||||
final Color backgroundColor;
|
||||
final Color spinnerColor;
|
||||
final Color textColor;
|
||||
final double blurAmount;
|
||||
final double spinnerSize;
|
||||
final bool showCard;
|
||||
|
||||
const LoadingSpinOverlay({
|
||||
super.key,
|
||||
this.message,
|
||||
this.backgroundColor = Colors.black54,
|
||||
this.spinnerColor = Colors.blue,
|
||||
this.textColor = Colors.white,
|
||||
this.blurAmount = 8.0,
|
||||
this.spinnerSize = 50.0,
|
||||
this.showCard = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LoadingSpinOverlay> createState() => _LoadingSpinOverlayState();
|
||||
}
|
||||
|
||||
class _LoadingSpinOverlayState extends State<LoadingSpinOverlay>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _fadeController;
|
||||
late AnimationController _rotationController;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<double> _rotationAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fadeController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
_rotationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 1),
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _fadeController,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
_rotationAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 2 * 3.14159,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _rotationController,
|
||||
curve: Curves.linear,
|
||||
));
|
||||
|
||||
_fadeController.forward();
|
||||
_rotationController.repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fadeController.dispose();
|
||||
_rotationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: widget.blurAmount,
|
||||
sigmaY: widget.blurAmount,
|
||||
),
|
||||
child: Container(
|
||||
color: widget.backgroundColor,
|
||||
child: Center(
|
||||
child: widget.showCard
|
||||
? _buildCardContent()
|
||||
: _buildSimpleContent(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCardContent() {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(32),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 280,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.92), // Semi-transparent
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 2,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Spinner simple de Flutter
|
||||
SizedBox(
|
||||
width: widget.spinnerSize,
|
||||
height: widget.spinnerSize,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(widget.spinnerColor),
|
||||
),
|
||||
),
|
||||
if (widget.message != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
widget.message!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey[800],
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSimpleContent() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: widget.spinnerSize,
|
||||
height: widget.spinnerSize,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(widget.spinnerColor),
|
||||
),
|
||||
),
|
||||
if (widget.message != null) ...[
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
widget.message!,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: widget.textColor,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Classe utilitaire pour gérer l'overlay de chargement avec spinner
|
||||
class LoadingSpinOverlayUtils {
|
||||
static OverlayEntry? _currentOverlay;
|
||||
|
||||
/// Affiche l'overlay de chargement avec spinner
|
||||
static OverlayEntry show({
|
||||
required BuildContext context,
|
||||
String? message,
|
||||
double blurAmount = 8.0,
|
||||
bool showCard = true,
|
||||
Color? spinnerColor,
|
||||
}) {
|
||||
// Fermer l'overlay existant s'il y en a un
|
||||
hide();
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final overlayEntry = OverlayEntry(
|
||||
builder: (context) => LoadingSpinOverlay(
|
||||
message: message,
|
||||
blurAmount: blurAmount,
|
||||
showCard: showCard,
|
||||
spinnerColor: spinnerColor ?? theme.colorScheme.primary,
|
||||
),
|
||||
);
|
||||
|
||||
_currentOverlay = overlayEntry;
|
||||
Overlay.of(context).insert(overlayEntry);
|
||||
return overlayEntry;
|
||||
}
|
||||
|
||||
/// Met à jour le message de l'overlay existant
|
||||
static void updateMessage({
|
||||
required OverlayEntry overlayEntry,
|
||||
String? message,
|
||||
}) {
|
||||
overlayEntry.markNeedsBuild();
|
||||
}
|
||||
|
||||
/// Cache l'overlay de chargement
|
||||
static void hide() {
|
||||
_currentOverlay?.remove();
|
||||
_currentOverlay = null;
|
||||
}
|
||||
|
||||
/// Cache un overlay spécifique
|
||||
static void hideSpecific(OverlayEntry? overlayEntry) {
|
||||
overlayEntry?.remove();
|
||||
if (_currentOverlay == overlayEntry) {
|
||||
_currentOverlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ class MembreRowWidget extends StatelessWidget {
|
||||
final Function(MembreModel)? onResetPassword;
|
||||
final bool isAlternate;
|
||||
final VoidCallback? onTap;
|
||||
final bool isMobile;
|
||||
|
||||
const MembreRowWidget({
|
||||
super.key,
|
||||
@@ -17,6 +18,7 @@ class MembreRowWidget extends StatelessWidget {
|
||||
this.onResetPassword,
|
||||
this.isAlternate = false,
|
||||
this.onTap,
|
||||
this.isMobile = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -37,20 +39,29 @@ class MembreRowWidget extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// ... existing row content ...
|
||||
|
||||
// ID
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
membre.id.toString() ?? '',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
// ID - masqué en mobile
|
||||
if (!isMobile)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
membre.id.toString() ?? '',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
|
||||
// Identifiant (username) - masqué en mobile
|
||||
if (!isMobile)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
membre.username ?? '',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Prénom
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: isMobile ? 2 : 2,
|
||||
child: Text(
|
||||
membre.firstName ?? '',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
@@ -59,47 +70,44 @@ class MembreRowWidget extends StatelessWidget {
|
||||
|
||||
// Nom
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: isMobile ? 2 : 2,
|
||||
child: Text(
|
||||
membre.name ?? '',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
|
||||
// Email
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
membre.email ?? '',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
// Email - masqué en mobile
|
||||
if (!isMobile)
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
membre.email ?? '',
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Rôle
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
_getRoleName(membre.role),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
// Rôle - masqué en mobile
|
||||
if (!isMobile)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
_getRoleName(membre.role),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Statut
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(membre.isActive),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: Text(
|
||||
membre.isActive == true ? 'Actif' : 'Inactif',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
child: Center(
|
||||
child: Tooltip(
|
||||
message: membre.isActive == true ? 'Actif' : 'Inactif',
|
||||
child: Icon(
|
||||
membre.isActive == true ? Icons.check_circle : Icons.cancel,
|
||||
color: membre.isActive == true ? Colors.green : Colors.red,
|
||||
size: 24,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -107,21 +115,21 @@ class MembreRowWidget extends StatelessWidget {
|
||||
// Actions
|
||||
if (onEdit != null || onDelete != null || onResetPassword != null)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: isMobile ? 2 : 2,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
// Bouton reset password (uniquement pour les membres actifs)
|
||||
if (onResetPassword != null && membre.isActive == true)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.lock_reset, size: 22),
|
||||
icon: Icon(Icons.lock_reset, size: isMobile ? 20 : 22),
|
||||
onPressed: () => onResetPassword!(membre),
|
||||
tooltip: 'Réinitialiser le mot de passe',
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
if (onDelete != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, size: 22),
|
||||
icon: Icon(Icons.delete, size: isMobile ? 20 : 22),
|
||||
onPressed: () => onDelete!(membre),
|
||||
tooltip: 'Supprimer',
|
||||
color: theme.colorScheme.error,
|
||||
|
||||
@@ -32,6 +32,8 @@ class MembreTableWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final isMobile = screenWidth < 768;
|
||||
|
||||
return Container(
|
||||
height: height,
|
||||
@@ -61,21 +63,35 @@ class MembreTableWidget extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// ID
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
'ID',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
// ID - masqué en mobile
|
||||
if (!isMobile)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
'ID',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Identifiant (username) - masqué en mobile
|
||||
if (!isMobile)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'Identifiant',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Prénom (firstName)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: isMobile ? 2 : 2,
|
||||
child: Text(
|
||||
'Prénom',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
@@ -87,7 +103,7 @@ class MembreTableWidget extends StatelessWidget {
|
||||
|
||||
// Nom (name)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: isMobile ? 2 : 2,
|
||||
child: Text(
|
||||
'Nom',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
@@ -97,29 +113,31 @@ class MembreTableWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
|
||||
// Email
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
'Email',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
// Email - masqué en mobile
|
||||
if (!isMobile)
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
'Email',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Rôle (fkRole)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
'Rôle',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
// Rôle (fkRole) - masqué en mobile
|
||||
if (!isMobile)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
'Rôle',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Statut
|
||||
Expanded(
|
||||
@@ -136,7 +154,7 @@ class MembreTableWidget extends StatelessWidget {
|
||||
// Actions (si onEdit, onDelete ou onResetPassword sont fournis)
|
||||
if (onEdit != null || onDelete != null || onResetPassword != null)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
flex: isMobile ? 2 : 2,
|
||||
child: Text(
|
||||
'Actions',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
@@ -152,14 +170,14 @@ class MembreTableWidget extends StatelessWidget {
|
||||
|
||||
// Corps du tableau
|
||||
Expanded(
|
||||
child: _buildTableContent(context),
|
||||
child: _buildTableContent(context, isMobile),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableContent(BuildContext context) {
|
||||
Widget _buildTableContent(BuildContext context, bool isMobile) {
|
||||
// Afficher un indicateur de chargement si isLoading est true
|
||||
if (isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@@ -193,6 +211,7 @@ class MembreTableWidget extends StatelessWidget {
|
||||
onResetPassword: onResetPassword,
|
||||
isAlternate: index % 2 == 1,
|
||||
onTap: onEdit != null ? () => onEdit!(membre) : null,
|
||||
isMobile: isMobile,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'dart:math';
|
||||
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'custom_text_field.dart';
|
||||
|
||||
class UserForm extends StatefulWidget {
|
||||
@@ -10,6 +13,8 @@ class UserForm extends StatefulWidget {
|
||||
final bool readOnly;
|
||||
final bool allowUsernameEdit;
|
||||
final bool allowSectNameEdit;
|
||||
final AmicaleModel? amicale; // Nouveau paramètre pour l'amicale
|
||||
final bool isAdmin; // Nouveau paramètre pour savoir si c'est un admin
|
||||
|
||||
const UserForm({
|
||||
super.key,
|
||||
@@ -18,6 +23,8 @@ class UserForm extends StatefulWidget {
|
||||
this.readOnly = false,
|
||||
this.allowUsernameEdit = false,
|
||||
this.allowSectNameEdit = false,
|
||||
this.amicale,
|
||||
this.isAdmin = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -37,12 +44,20 @@ class _UserFormState extends State<UserForm> {
|
||||
late final TextEditingController _emailController;
|
||||
late final TextEditingController _dateNaissanceController;
|
||||
late final TextEditingController _dateEmbaucheController;
|
||||
late final TextEditingController _passwordController; // Nouveau controller pour le mot de passe
|
||||
|
||||
// Form values
|
||||
int _fkTitre = 1; // 1 = M., 2 = Mme
|
||||
DateTime? _dateNaissance;
|
||||
DateTime? _dateEmbauche;
|
||||
|
||||
// Pour la génération automatique d'username
|
||||
bool _isGeneratingUsername = false;
|
||||
final Random _random = Random();
|
||||
|
||||
// Pour afficher/masquer le mot de passe
|
||||
bool _obscurePassword = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -64,11 +79,34 @@ class _UserFormState extends State<UserForm> {
|
||||
|
||||
_dateEmbaucheController = TextEditingController(text: _dateEmbauche != null ? DateFormat('dd/MM/yyyy').format(_dateEmbauche!) : '');
|
||||
|
||||
_passwordController = TextEditingController(); // Initialiser le controller du mot de passe
|
||||
|
||||
_fkTitre = user?.fkTitre ?? 1;
|
||||
|
||||
// Ajouter des listeners pour auto-générer l'username en création
|
||||
if (widget.user?.id == 0 && widget.isAdmin && widget.amicale?.chkUsernameManuel == true) {
|
||||
_nameController.addListener(_onNameOrSectNameChanged);
|
||||
_sectNameController.addListener(_onNameOrSectNameChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void _onNameOrSectNameChanged() {
|
||||
// Auto-générer username seulement en création et si le champ username est vide
|
||||
if (widget.user?.id == 0 &&
|
||||
_usernameController.text.isEmpty &&
|
||||
(_nameController.text.isNotEmpty || _sectNameController.text.isNotEmpty)) {
|
||||
_generateAndCheckUsername();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Retirer les listeners si ajoutés
|
||||
if (widget.user?.id == 0 && widget.isAdmin && widget.amicale?.chkUsernameManuel == true) {
|
||||
_nameController.removeListener(_onNameOrSectNameChanged);
|
||||
_sectNameController.removeListener(_onNameOrSectNameChanged);
|
||||
}
|
||||
|
||||
_usernameController.dispose();
|
||||
_firstNameController.dispose();
|
||||
_nameController.dispose();
|
||||
@@ -78,6 +116,7 @@ class _UserFormState extends State<UserForm> {
|
||||
_emailController.dispose();
|
||||
_dateNaissanceController.dispose();
|
||||
_dateEmbaucheController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -98,13 +137,49 @@ class _UserFormState extends State<UserForm> {
|
||||
void _selectDate(BuildContext context, bool isDateNaissance) {
|
||||
// Utiliser un bloc try-catch pour capturer toutes les erreurs possibles
|
||||
try {
|
||||
// Afficher le sélecteur de date sans spécifier de locale
|
||||
// Déterminer la date initiale
|
||||
DateTime initialDate;
|
||||
if (isDateNaissance) {
|
||||
initialDate = _dateNaissance ?? DateTime.now().subtract(const Duration(days: 365 * 30)); // 30 ans par défaut
|
||||
} else {
|
||||
initialDate = _dateEmbauche ?? DateTime.now();
|
||||
}
|
||||
|
||||
// S'assurer que la date initiale est dans la plage autorisée
|
||||
if (initialDate.isAfter(DateTime.now())) {
|
||||
initialDate = DateTime.now();
|
||||
}
|
||||
if (initialDate.isBefore(DateTime(1900))) {
|
||||
initialDate = DateTime(1950);
|
||||
}
|
||||
|
||||
// Afficher le sélecteur de date avec locale française
|
||||
showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(), // Toujours utiliser la date actuelle
|
||||
initialDate: initialDate,
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
// Ne pas spécifier de locale pour éviter les problèmes
|
||||
locale: const Locale('fr', 'FR'), // Forcer la locale française
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: Theme.of(context).colorScheme.copyWith(
|
||||
primary: Theme.of(context).colorScheme.primary,
|
||||
onPrimary: Colors.white,
|
||||
surface: Colors.white,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
helpText: isDateNaissance ? 'SÉLECTIONNER LA DATE DE NAISSANCE' : 'SÉLECTIONNER LA DATE D\'EMBAUCHE',
|
||||
cancelText: 'ANNULER',
|
||||
confirmText: 'VALIDER',
|
||||
fieldLabelText: 'Entrer une date',
|
||||
fieldHintText: 'jj/mm/aaaa',
|
||||
errorFormatText: 'Format de date invalide',
|
||||
errorInvalidText: 'Date invalide',
|
||||
).then((DateTime? picked) {
|
||||
// Vérifier si une date a été sélectionnée
|
||||
if (picked != null) {
|
||||
@@ -141,30 +216,247 @@ class _UserFormState extends State<UserForm> {
|
||||
}
|
||||
}
|
||||
|
||||
// Nettoyer une chaîne pour l'username
|
||||
String _cleanString(String input) {
|
||||
return input.toLowerCase()
|
||||
.replaceAll(RegExp(r'[^a-z0-9]'), ''); // Garder seulement lettres et chiffres
|
||||
}
|
||||
|
||||
// Extraire une partie aléatoire d'une chaîne
|
||||
String _extractRandomPart(String input, int minLength, int maxLength) {
|
||||
if (input.isEmpty) return '';
|
||||
final cleaned = _cleanString(input);
|
||||
if (cleaned.isEmpty) return '';
|
||||
|
||||
final length = minLength + _random.nextInt(maxLength - minLength + 1);
|
||||
if (cleaned.length <= length) return cleaned;
|
||||
|
||||
// Prendre les premiers caractères jusqu'à la longueur désirée
|
||||
return cleaned.substring(0, length);
|
||||
}
|
||||
|
||||
// Générer un username selon l'algorithme spécifié
|
||||
String _generateUsername() {
|
||||
// Récupérer les données nécessaires
|
||||
final nom = _nameController.text.isNotEmpty ? _nameController.text : _sectNameController.text;
|
||||
final codePostal = widget.amicale?.codePostal ?? '';
|
||||
final ville = widget.amicale?.ville ?? '';
|
||||
|
||||
// Nettoyer et extraire les parties
|
||||
final nomPart = _extractRandomPart(nom, 2, 5);
|
||||
final cpPart = _extractRandomPart(codePostal, 2, 3);
|
||||
final villePart = _extractRandomPart(ville, 2, 4);
|
||||
final nombreAleatoire = 10 + _random.nextInt(990); // 10 à 999
|
||||
|
||||
// Choisir des séparateurs aléatoires (uniquement ceux autorisés: ., _, -)
|
||||
final separateurs = ['', '.', '_', '-'];
|
||||
final sep1 = separateurs[_random.nextInt(separateurs.length)];
|
||||
final sep2 = separateurs[_random.nextInt(separateurs.length)];
|
||||
|
||||
// Assembler l'username
|
||||
String username = '$nomPart$sep1$cpPart$sep2$villePart$nombreAleatoire';
|
||||
|
||||
// Si trop court, ajouter des chiffres pour atteindre minimum 10 caractères
|
||||
while (username.length < 10) {
|
||||
username += _random.nextInt(10).toString();
|
||||
}
|
||||
|
||||
// S'assurer que l'username ne contient que des caractères autorisés (a-z, 0-9, ., -, _)
|
||||
// Normalement déjà le cas avec notre algorithme, mais au cas où
|
||||
username = username.toLowerCase().replaceAll(RegExp(r'[^a-z0-9._-]'), '');
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
// Vérifier la disponibilité d'un username via l'API
|
||||
Future<Map<String, dynamic>> _checkUsernameAvailability(String username) async {
|
||||
try {
|
||||
final response = await ApiService.instance.post(
|
||||
'/users/check-username',
|
||||
data: {'username': username},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return response.data;
|
||||
}
|
||||
return {'available': false};
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification de l\'username: $e');
|
||||
return {'available': false};
|
||||
}
|
||||
}
|
||||
|
||||
// Générer et vérifier un username jusqu'à en trouver un disponible
|
||||
Future<void> _generateAndCheckUsername() async {
|
||||
if (_isGeneratingUsername) return; // Éviter les appels multiples
|
||||
|
||||
setState(() {
|
||||
_isGeneratingUsername = true;
|
||||
});
|
||||
|
||||
try {
|
||||
int attempts = 0;
|
||||
const maxAttempts = 10;
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
final username = _generateUsername();
|
||||
debugPrint('Tentative ${attempts + 1}: Vérification de $username');
|
||||
|
||||
final result = await _checkUsernameAvailability(username);
|
||||
|
||||
if (result['available'] == true) {
|
||||
// Username disponible, l'utiliser
|
||||
setState(() {
|
||||
_usernameController.text = username;
|
||||
});
|
||||
debugPrint('✅ Username disponible trouvé: $username');
|
||||
break;
|
||||
} else {
|
||||
// Si l'API propose des suggestions, essayer la première
|
||||
if (result['suggestions'] != null && result['suggestions'].isNotEmpty) {
|
||||
final suggestion = result['suggestions'][0];
|
||||
debugPrint('Vérification de la suggestion: $suggestion');
|
||||
|
||||
final suggestionResult = await _checkUsernameAvailability(suggestion);
|
||||
if (suggestionResult['available'] == true) {
|
||||
setState(() {
|
||||
_usernameController.text = suggestion;
|
||||
});
|
||||
debugPrint('✅ Suggestion disponible utilisée: $suggestion');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (attempts >= maxAttempts) {
|
||||
debugPrint('⚠️ Impossible de trouver un username disponible après $maxAttempts tentatives');
|
||||
}
|
||||
} finally {
|
||||
setState(() {
|
||||
_isGeneratingUsername = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Valider le mot de passe selon les règles
|
||||
String? _validatePassword(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
// Pour un nouveau membre, le mot de passe est obligatoire si le champ est affiché
|
||||
if (widget.user?.id == 0) {
|
||||
return "Veuillez entrer un mot de passe";
|
||||
}
|
||||
return null; // En modification, vide = garder l'ancien
|
||||
}
|
||||
|
||||
// Faire un trim pour retirer les espaces en début/fin
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isEmpty) {
|
||||
return "Le mot de passe ne peut pas être vide";
|
||||
}
|
||||
|
||||
// Vérifier qu'il n'y a pas d'espaces dans le mot de passe
|
||||
if (trimmedValue.contains(' ')) {
|
||||
return "Le mot de passe ne doit pas contenir d'espaces";
|
||||
}
|
||||
|
||||
// Vérifier la longueur
|
||||
if (trimmedValue.length < 12) {
|
||||
return "Le mot de passe doit contenir au moins 12 caractères";
|
||||
}
|
||||
if (trimmedValue.length > 16) {
|
||||
return "Le mot de passe ne doit pas dépasser 16 caractères";
|
||||
}
|
||||
|
||||
// Vérifier qu'il n'est pas égal au username (après trim des deux)
|
||||
if (trimmedValue == _usernameController.text.trim()) {
|
||||
return "Le mot de passe ne doit pas être identique au nom d'utilisateur";
|
||||
}
|
||||
|
||||
// Vérifier la présence d'au moins une minuscule
|
||||
if (!trimmedValue.contains(RegExp(r'[a-z]'))) {
|
||||
return "Le mot de passe doit contenir au moins une lettre minuscule";
|
||||
}
|
||||
|
||||
// Vérifier la présence d'au moins une majuscule
|
||||
if (!trimmedValue.contains(RegExp(r'[A-Z]'))) {
|
||||
return "Le mot de passe doit contenir au moins une lettre majuscule";
|
||||
}
|
||||
|
||||
// Vérifier la présence d'au moins un chiffre
|
||||
if (!trimmedValue.contains(RegExp(r'[0-9]'))) {
|
||||
return "Le mot de passe doit contenir au moins un chiffre";
|
||||
}
|
||||
|
||||
// Vérifier la présence d'au moins un caractère spécial
|
||||
if (!trimmedValue.contains(RegExp(r'[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]'))) {
|
||||
return "Le mot de passe doit contenir au moins un caractère spécial (!@#\$%^&*()_+-=[]{}|;:,.<>?)";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Générer un mot de passe aléatoire respectant les règles
|
||||
String _generatePassword() {
|
||||
const String lowercase = 'abcdefghijklmnopqrstuvwxyz';
|
||||
const String uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
const String digits = '0123456789';
|
||||
const String special = '!@#\$%^&*()_+-=[]{}|;:,.<>?';
|
||||
|
||||
// Longueur aléatoire entre 12 et 16
|
||||
final length = 12 + _random.nextInt(5);
|
||||
|
||||
// S'assurer d'avoir au moins un caractère de chaque type
|
||||
List<String> password = [];
|
||||
password.add(lowercase[_random.nextInt(lowercase.length)]);
|
||||
password.add(uppercase[_random.nextInt(uppercase.length)]);
|
||||
password.add(digits[_random.nextInt(digits.length)]);
|
||||
password.add(special[_random.nextInt(special.length)]);
|
||||
|
||||
// Compléter avec des caractères aléatoires
|
||||
const String allChars = lowercase + uppercase + digits + special;
|
||||
for (int i = password.length; i < length; i++) {
|
||||
password.add(allChars[_random.nextInt(allChars.length)]);
|
||||
}
|
||||
|
||||
// Mélanger les caractères
|
||||
password.shuffle(_random);
|
||||
|
||||
return password.join('');
|
||||
}
|
||||
|
||||
// Méthode publique pour récupérer le mot de passe si défini
|
||||
String? getPassword() {
|
||||
final password = _passwordController.text.trim();
|
||||
return password.isNotEmpty ? password : null;
|
||||
}
|
||||
|
||||
// Méthode publique pour valider et récupérer l'utilisateur
|
||||
UserModel? validateAndGetUser() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
return widget.user?.copyWith(
|
||||
username: _usernameController.text,
|
||||
firstName: _firstNameController.text,
|
||||
name: _nameController.text,
|
||||
sectName: _sectNameController.text,
|
||||
phone: _phoneController.text,
|
||||
mobile: _mobileController.text,
|
||||
email: _emailController.text,
|
||||
username: _usernameController.text.trim(), // Appliquer trim
|
||||
firstName: _firstNameController.text.trim(),
|
||||
name: _nameController.text.trim(),
|
||||
sectName: _sectNameController.text.trim(),
|
||||
phone: _phoneController.text.trim(),
|
||||
mobile: _mobileController.text.trim(),
|
||||
email: _emailController.text.trim(),
|
||||
fkTitre: _fkTitre,
|
||||
dateNaissance: _dateNaissance,
|
||||
dateEmbauche: _dateEmbauche,
|
||||
) ??
|
||||
UserModel(
|
||||
id: 0,
|
||||
username: _usernameController.text,
|
||||
firstName: _firstNameController.text,
|
||||
name: _nameController.text,
|
||||
sectName: _sectNameController.text,
|
||||
phone: _phoneController.text,
|
||||
mobile: _mobileController.text,
|
||||
email: _emailController.text,
|
||||
username: _usernameController.text.trim(), // Appliquer trim
|
||||
firstName: _firstNameController.text.trim(),
|
||||
name: _nameController.text.trim(),
|
||||
sectName: _sectNameController.text.trim(),
|
||||
phone: _phoneController.text.trim(),
|
||||
mobile: _mobileController.text.trim(),
|
||||
email: _emailController.text.trim(),
|
||||
fkTitre: _fkTitre,
|
||||
dateNaissance: _dateNaissance,
|
||||
dateEmbauche: _dateEmbauche,
|
||||
@@ -181,89 +473,35 @@ class _UserFormState extends State<UserForm> {
|
||||
final theme = Theme.of(context);
|
||||
final isWideScreen = MediaQuery.of(context).size.width > 900;
|
||||
|
||||
// Déterminer si on doit afficher le champ username selon les règles
|
||||
final bool shouldShowUsernameField = widget.isAdmin && widget.amicale?.chkUsernameManuel == true;
|
||||
// Déterminer si le username est éditable (seulement en création, jamais en modification)
|
||||
final bool canEditUsername = shouldShowUsernameField && widget.allowUsernameEdit && widget.user?.id == 0;
|
||||
// Déterminer si on doit afficher le champ mot de passe
|
||||
final bool shouldShowPasswordField = widget.isAdmin && widget.amicale?.chkMdpManuel == true;
|
||||
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Ligne 1: Username et Email (si écran large)
|
||||
if (isWideScreen)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _usernameController,
|
||||
label: "Nom d'utilisateur",
|
||||
readOnly: !widget.allowUsernameEdit, // Utiliser le paramètre
|
||||
prefixIcon: Icons.account_circle,
|
||||
isRequired: widget.allowUsernameEdit,
|
||||
validator: widget.allowUsernameEdit
|
||||
? (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer le nom d'utilisateur";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _emailController,
|
||||
label: "Email",
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
readOnly: widget.readOnly,
|
||||
isRequired: true, // Email toujours obligatoire
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer l'adresse email";
|
||||
}
|
||||
if (!value.contains('@') || !value.contains('.')) {
|
||||
return "Veuillez entrer une adresse email valide";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else ...[
|
||||
// Version mobile: Username seul
|
||||
CustomTextField(
|
||||
controller: _usernameController,
|
||||
label: "Nom d'utilisateur",
|
||||
readOnly: !widget.allowUsernameEdit, // Utiliser le paramètre
|
||||
prefixIcon: Icons.account_circle,
|
||||
isRequired: widget.allowUsernameEdit, // Obligatoire si éditable
|
||||
validator: widget.allowUsernameEdit
|
||||
? (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer le nom d'utilisateur";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Email seul en mobile
|
||||
CustomTextField(
|
||||
controller: _emailController,
|
||||
label: "Email",
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
readOnly: widget.readOnly,
|
||||
isRequired: true, // Email toujours obligatoire
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer l'adresse email";
|
||||
}
|
||||
if (!value.contains('@') || !value.contains('.')) {
|
||||
return "Veuillez entrer une adresse email valide";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
// Email seul sur la première ligne
|
||||
CustomTextField(
|
||||
controller: _emailController,
|
||||
label: "Email",
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
readOnly: widget.readOnly,
|
||||
isRequired: true,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer l'adresse email";
|
||||
}
|
||||
if (!value.contains('@') || !value.contains('.')) {
|
||||
return "Veuillez entrer une adresse email valide";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Titre (M. ou Mme)
|
||||
@@ -378,7 +616,7 @@ class _UserFormState extends State<UserForm> {
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Ligne 3: Téléphones (fixe et mobile)
|
||||
// Ligne 2: Téléphones (fixe et mobile)
|
||||
if (isWideScreen)
|
||||
Row(
|
||||
children: [
|
||||
@@ -459,6 +697,224 @@ class _UserFormState extends State<UserForm> {
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Ligne 3: Username et Password (si applicable)
|
||||
if (shouldShowUsernameField || shouldShowPasswordField) ...[
|
||||
if (isWideScreen)
|
||||
Row(
|
||||
children: [
|
||||
if (shouldShowUsernameField)
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _usernameController,
|
||||
label: "Nom d'utilisateur",
|
||||
readOnly: !canEditUsername,
|
||||
prefixIcon: Icons.account_circle,
|
||||
isRequired: canEditUsername,
|
||||
suffixIcon: (widget.user?.id == 0 && canEditUsername)
|
||||
? _isGeneratingUsername
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
: IconButton(
|
||||
icon: Icon(Icons.refresh),
|
||||
onPressed: _generateAndCheckUsername,
|
||||
tooltip: "Générer un nom d'utilisateur",
|
||||
)
|
||||
: null,
|
||||
helperText: canEditUsername
|
||||
? "Min. 10 caractères (a-z, 0-9, . - _)"
|
||||
: null,
|
||||
validator: canEditUsername
|
||||
? (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer le nom d'utilisateur";
|
||||
}
|
||||
// Faire un trim pour retirer les espaces en début/fin
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isEmpty) {
|
||||
return "Le nom d'utilisateur ne peut pas être vide";
|
||||
}
|
||||
// Vérifier qu'il n'y a pas d'espaces dans l'username
|
||||
if (trimmedValue.contains(' ')) {
|
||||
return "Le nom d'utilisateur ne doit pas contenir d'espaces";
|
||||
}
|
||||
// Vérifier la longueur minimale
|
||||
if (trimmedValue.length < 10) {
|
||||
return "Le nom d'utilisateur doit contenir au moins 10 caractères";
|
||||
}
|
||||
// Vérifier les caractères autorisés (a-z, 0-9, ., -, _)
|
||||
if (!RegExp(r'^[a-z0-9._-]+$').hasMatch(trimmedValue)) {
|
||||
return "Caractères autorisés : lettres minuscules, chiffres, . - _";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
: null,
|
||||
),
|
||||
),
|
||||
if (shouldShowUsernameField && shouldShowPasswordField)
|
||||
const SizedBox(width: 16),
|
||||
if (shouldShowPasswordField)
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _passwordController,
|
||||
label: "Mot de passe",
|
||||
obscureText: _obscurePassword,
|
||||
readOnly: widget.readOnly,
|
||||
prefixIcon: Icons.lock,
|
||||
suffixIcon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Bouton pour afficher/masquer le mot de passe
|
||||
IconButton(
|
||||
icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
});
|
||||
},
|
||||
tooltip: _obscurePassword ? "Afficher le mot de passe" : "Masquer le mot de passe",
|
||||
),
|
||||
// Bouton pour générer un mot de passe (seulement si éditable)
|
||||
if (!widget.readOnly)
|
||||
IconButton(
|
||||
icon: Icon(Icons.auto_awesome),
|
||||
onPressed: () {
|
||||
final newPassword = _generatePassword();
|
||||
setState(() {
|
||||
_passwordController.text = newPassword;
|
||||
_obscurePassword = false; // Afficher le mot de passe généré
|
||||
});
|
||||
// Revalider le formulaire
|
||||
_formKey.currentState?.validate();
|
||||
},
|
||||
tooltip: "Générer un mot de passe sécurisé",
|
||||
),
|
||||
],
|
||||
),
|
||||
helperText: widget.user?.id != 0
|
||||
? "Laissez vide pour conserver le mot de passe actuel"
|
||||
: "12-16 car. avec min/maj, chiffres et spéciaux (!@#\$%^&*()_+-=[]{}|;:,.<>?)",
|
||||
validator: _validatePassword,
|
||||
),
|
||||
),
|
||||
// Si seulement un des deux est affiché, ajouter un Expanded vide pour garder l'alignement
|
||||
if ((shouldShowUsernameField && !shouldShowPasswordField) || (!shouldShowUsernameField && shouldShowPasswordField))
|
||||
const Expanded(child: SizedBox()),
|
||||
],
|
||||
)
|
||||
else ...[
|
||||
// Version mobile: Username et Password séparés
|
||||
if (shouldShowUsernameField) ...[
|
||||
CustomTextField(
|
||||
controller: _usernameController,
|
||||
label: "Nom d'utilisateur",
|
||||
readOnly: !canEditUsername,
|
||||
prefixIcon: Icons.account_circle,
|
||||
isRequired: canEditUsername,
|
||||
suffixIcon: (widget.user?.id == 0 && canEditUsername)
|
||||
? _isGeneratingUsername
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
: IconButton(
|
||||
icon: Icon(Icons.refresh),
|
||||
onPressed: _generateAndCheckUsername,
|
||||
tooltip: "Générer un nom d'utilisateur",
|
||||
)
|
||||
: null,
|
||||
helperText: canEditUsername
|
||||
? "Min. 10 caractères (a-z, 0-9, . - _)"
|
||||
: null,
|
||||
validator: canEditUsername
|
||||
? (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer le nom d'utilisateur";
|
||||
}
|
||||
// Faire un trim pour retirer les espaces en début/fin
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isEmpty) {
|
||||
return "Le nom d'utilisateur ne peut pas être vide";
|
||||
}
|
||||
// Vérifier qu'il n'y a pas d'espaces dans l'username
|
||||
if (trimmedValue.contains(' ')) {
|
||||
return "Le nom d'utilisateur ne doit pas contenir d'espaces";
|
||||
}
|
||||
// Vérifier la longueur minimale
|
||||
if (trimmedValue.length < 10) {
|
||||
return "Le nom d'utilisateur doit contenir au moins 10 caractères";
|
||||
}
|
||||
// Vérifier les caractères autorisés (a-z, 0-9, ., -, _)
|
||||
if (!RegExp(r'^[a-z0-9._-]+$').hasMatch(trimmedValue)) {
|
||||
return "Caractères autorisés : lettres minuscules, chiffres, . - _";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
if (shouldShowPasswordField) ...[
|
||||
CustomTextField(
|
||||
controller: _passwordController,
|
||||
label: "Mot de passe",
|
||||
obscureText: _obscurePassword,
|
||||
readOnly: widget.readOnly,
|
||||
prefixIcon: Icons.lock,
|
||||
suffixIcon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Bouton pour afficher/masquer le mot de passe
|
||||
IconButton(
|
||||
icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
});
|
||||
},
|
||||
tooltip: _obscurePassword ? "Afficher le mot de passe" : "Masquer le mot de passe",
|
||||
),
|
||||
// Bouton pour générer un mot de passe (seulement si éditable)
|
||||
if (!widget.readOnly)
|
||||
IconButton(
|
||||
icon: Icon(Icons.auto_awesome),
|
||||
onPressed: () {
|
||||
final newPassword = _generatePassword();
|
||||
setState(() {
|
||||
_passwordController.text = newPassword;
|
||||
_obscurePassword = false; // Afficher le mot de passe généré
|
||||
});
|
||||
// Revalider le formulaire
|
||||
_formKey.currentState?.validate();
|
||||
},
|
||||
tooltip: "Générer un mot de passe sécurisé",
|
||||
),
|
||||
],
|
||||
),
|
||||
helperText: widget.user?.id != 0
|
||||
? "Laissez vide pour conserver le mot de passe actuel"
|
||||
: "12-16 car. avec min/maj, chiffres et spéciaux (!@#\$%^&*()_+-=[]{}|;:,.<>?)",
|
||||
validator: _validatePassword,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Ligne 4: Dates (naissance et embauche)
|
||||
if (isWideScreen)
|
||||
Row(
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/presentation/widgets/user_form.dart';
|
||||
|
||||
class UserFormDialog extends StatefulWidget {
|
||||
final UserModel? user;
|
||||
final String title;
|
||||
final bool readOnly;
|
||||
final Function(UserModel)? onSubmit;
|
||||
final Function(UserModel, {String? password})? onSubmit; // Modifié pour inclure le mot de passe
|
||||
final bool showRoleSelector;
|
||||
final List<RoleOption>? availableRoles;
|
||||
final bool showActiveCheckbox;
|
||||
final bool allowUsernameEdit;
|
||||
final AmicaleModel? amicale; // Nouveau paramètre
|
||||
final bool isAdmin; // Nouveau paramètre
|
||||
|
||||
const UserFormDialog({
|
||||
super.key,
|
||||
@@ -22,6 +25,8 @@ class UserFormDialog extends StatefulWidget {
|
||||
this.availableRoles,
|
||||
this.showActiveCheckbox = false,
|
||||
this.allowUsernameEdit = false,
|
||||
this.amicale,
|
||||
this.isAdmin = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -55,6 +60,7 @@ class _UserFormDialogState extends State<UserFormDialog> {
|
||||
void _handleSubmit() async {
|
||||
// Utiliser la méthode validateAndGetUser du UserForm
|
||||
final userData = _userFormKey.currentState?.validateAndGetUser();
|
||||
final password = _userFormKey.currentState?.getPassword(); // Récupérer le mot de passe
|
||||
|
||||
if (userData != null) {
|
||||
var finalUser = userData;
|
||||
@@ -70,7 +76,7 @@ class _UserFormDialogState extends State<UserFormDialog> {
|
||||
}
|
||||
|
||||
if (widget.onSubmit != null) {
|
||||
widget.onSubmit!(finalUser);
|
||||
widget.onSubmit!(finalUser, password: password); // Passer le mot de passe
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,6 +206,8 @@ class _UserFormDialogState extends State<UserFormDialog> {
|
||||
readOnly: widget.readOnly,
|
||||
allowUsernameEdit: widget.allowUsernameEdit,
|
||||
allowSectNameEdit: widget.allowUsernameEdit,
|
||||
amicale: widget.amicale, // Passer l'amicale
|
||||
isAdmin: widget.isAdmin, // Passer isAdmin
|
||||
onSubmit: null, // Pas besoin de callback
|
||||
),
|
||||
],
|
||||
|
||||
1
app/linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux
Symbolic link
1
app/linux/flutter/ephemeral/.plugin_symlinks/file_selector_linux
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/pierre/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.3+2/
|
||||
1
app/linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux
Symbolic link
1
app/linux/flutter/ephemeral/.plugin_symlinks/image_picker_linux
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/pierre/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1+2/
|
||||
@@ -6,9 +6,13 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import connectivity_plus
|
||||
import file_selector_macos
|
||||
import flutter_local_notifications
|
||||
import geolocator_apple
|
||||
import package_info_plus
|
||||
@@ -15,6 +16,7 @@ import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
|
||||
@@ -3,8 +3,8 @@ FLUTTER_ROOT=/home/pierre/dev/flutter
|
||||
FLUTTER_APPLICATION_PATH=/home/pierre/dev/geosector/app
|
||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||
FLUTTER_BUILD_DIR=build
|
||||
FLUTTER_BUILD_NAME=3.0.4
|
||||
FLUTTER_BUILD_NUMBER=304
|
||||
FLUTTER_BUILD_NAME=3.0.6
|
||||
FLUTTER_BUILD_NUMBER=306
|
||||
DART_OBFUSCATION=false
|
||||
TRACK_WIDGET_CREATION=true
|
||||
TREE_SHAKE_ICONS=false
|
||||
|
||||
@@ -4,8 +4,8 @@ export "FLUTTER_ROOT=/home/pierre/dev/flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=/home/pierre/dev/geosector/app"
|
||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
export "FLUTTER_BUILD_NAME=3.0.4"
|
||||
export "FLUTTER_BUILD_NUMBER=304"
|
||||
export "FLUTTER_BUILD_NAME=3.0.6"
|
||||
export "FLUTTER_BUILD_NUMBER=306"
|
||||
export "DART_OBFUSCATION=false"
|
||||
export "TRACK_WIDGET_CREATION=true"
|
||||
export "TREE_SHAKE_ICONS=false"
|
||||
|
||||
145
app/pubspec.lock
145
app/pubspec.lock
@@ -114,10 +114,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62"
|
||||
sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.11.0"
|
||||
version: "8.11.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -198,6 +198,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -258,10 +266,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
||||
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.8.0+1"
|
||||
version: "5.9.0"
|
||||
dio_cache_interceptor:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -318,6 +326,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+2"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4+3"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.2"
|
||||
file_selector_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -387,6 +427,11 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_map:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -403,6 +448,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0+1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.29"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -569,10 +622,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "85ab0074f9bf2b24625906d8382bbec84d3d6919d285ba9c106b07b65791fb99"
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0-beta.2"
|
||||
version: "1.5.0"
|
||||
http_cache_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -613,6 +666,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: b08e9a04d0f8d91f4a6e767a745b9871bfbc585410205c311d0492de20a7ccd6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+25"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+2"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.1"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -945,10 +1062,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
|
||||
sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.10"
|
||||
version: "2.4.11"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1078,18 +1195,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: syncfusion_flutter_charts
|
||||
sha256: "55bc6210265483eba4ea168931e4edd3df24510d1ea99c177df46d69ab0deb6f"
|
||||
sha256: a2263d6221b17d49ea9c23709df8796abde4cd2d2664960d48fac62b9d8c3eb8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "30.1.42"
|
||||
version: "30.2.4"
|
||||
syncfusion_flutter_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_flutter_core
|
||||
sha256: f999f11af9c54bd46ceb478273e06774d28723ffa2c622d2d6e7e84a358c23e0
|
||||
sha256: "9d6b7722331e8c84e837d852da03f3735bf436fe6ebf2acc3455a2a21a0d47ff"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "30.1.42"
|
||||
version: "30.2.4"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1174,10 +1291,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
|
||||
sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.16"
|
||||
version: "6.3.17"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: geosector_app
|
||||
description: 'GEOSECTOR - Gestion de distribution des calendriers par secteurs géographiques pour les amicales de pompiers'
|
||||
publish_to: 'none'
|
||||
version: 3.0.4+304
|
||||
version: 3.0.6+306
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
@@ -9,6 +9,8 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.6
|
||||
|
||||
# Navigation
|
||||
@@ -49,6 +51,9 @@ dependencies:
|
||||
mqtt5_client: ^4.11.0
|
||||
flutter_local_notifications: ^19.0.1
|
||||
|
||||
# Upload d'images
|
||||
image_picker: ^1.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/home/pierre/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3+4/
|
||||
@@ -0,0 +1 @@
|
||||
/home/pierre/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1+1/
|
||||
@@ -7,12 +7,15 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <geolocator_windows/geolocator_windows.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
GeolocatorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus
|
||||
file_selector_windows
|
||||
geolocator_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user