4d936d2ee3
App Flutter de gestion de stock/prix pour tablette Android (100% local) : scan code-barres (OpenFoodFacts), prix au kilo/litre, packs, inventaire, prix d'achat + marge, filtres de tri, étiquettes PDF, design noir & blanc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
85 lines
2.7 KiB
Dart
85 lines
2.7 KiB
Dart
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../models/produit.dart';
|
|
|
|
/// Résultat d'une recherche produit sur OpenFoodFacts.
|
|
class InfoProduit {
|
|
final String? nom;
|
|
final String? imageUrl;
|
|
final String? marque;
|
|
final double? quantite;
|
|
final String? unite;
|
|
|
|
const InfoProduit({
|
|
this.nom,
|
|
this.imageUrl,
|
|
this.marque,
|
|
this.quantite,
|
|
this.unite,
|
|
});
|
|
|
|
bool get estVide => (nom == null || nom!.isEmpty) && imageUrl == null;
|
|
}
|
|
|
|
/// Interroge l'API publique OpenFoodFacts pour récupérer le nom, l'image et la
|
|
/// quantité (grammage / litrage) d'un produit à partir de son code-barres.
|
|
class OpenFoodFactsService {
|
|
static const String _base = 'https://world.openfoodfacts.org/api/v2/product';
|
|
|
|
Future<InfoProduit?> chercherParCodeBarres(String codeBarres) async {
|
|
final uri = Uri.parse(
|
|
'$_base/$codeBarres.json?fields=product_name,product_name_fr,brands,'
|
|
'image_front_url,image_url,product_quantity,product_quantity_unit',
|
|
);
|
|
|
|
try {
|
|
final res = await http
|
|
.get(uri, headers: {'User-Agent': 'GestionPrixProduit/1.0'})
|
|
.timeout(const Duration(seconds: 8));
|
|
|
|
if (res.statusCode != 200) return null;
|
|
|
|
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
|
// status == 1 => produit trouvé
|
|
if (data['status'] != 1) return const InfoProduit();
|
|
|
|
final p = data['product'] as Map<String, dynamic>? ?? {};
|
|
final nom = (p['product_name_fr'] as String?)?.trim();
|
|
final nomEn = (p['product_name'] as String?)?.trim();
|
|
final image = (p['image_front_url'] as String?)?.trim() ??
|
|
(p['image_url'] as String?)?.trim();
|
|
|
|
final (quantite, unite) = _quantite(p);
|
|
|
|
return InfoProduit(
|
|
nom: (nom != null && nom.isNotEmpty) ? nom : nomEn,
|
|
imageUrl: (image != null && image.isNotEmpty) ? image : null,
|
|
marque: (p['brands'] as String?)?.split(',').first.trim(),
|
|
quantite: quantite,
|
|
unite: unite,
|
|
);
|
|
} catch (_) {
|
|
// Pas de réseau ou timeout : on retourne null, l'utilisateur saisit à la main.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Normalise la quantité OpenFoodFacts (product_quantity + unité) vers nos unités.
|
|
(double?, String?) _quantite(Map<String, dynamic> p) {
|
|
final q = (p['product_quantity'] as num?)?.toDouble();
|
|
final u = (p['product_quantity_unit'] as String?)?.toLowerCase().trim();
|
|
if (q == null || q <= 0 || u == null) return (null, null);
|
|
|
|
return switch (u) {
|
|
'g' => (q, Unites.g),
|
|
'kg' => (q, Unites.kg),
|
|
'mg' => (q / 1000, Unites.g),
|
|
'ml' => (q, Unites.ml),
|
|
'cl' => (q, Unites.cl),
|
|
'l' => (q, Unites.l),
|
|
_ => (null, null),
|
|
};
|
|
}
|
|
}
|