import 'package:flutter/material.dart'; import 'package:printing/printing.dart'; import '../models/produit.dart'; import '../services/etiquette_pdf.dart'; import '../services/produit_repository.dart'; import '../theme.dart'; import '../utils/format.dart'; import '../utils/tri.dart'; import '../widgets/produit_image.dart'; /// Onglet 3 : sélection de produits puis génération/impression des étiquettes. class EtiquettesScreen extends StatefulWidget { const EtiquettesScreen({super.key}); @override State createState() => _EtiquettesScreenState(); } class _EtiquettesScreenState extends State { final _repo = ProduitRepository.instance; final Set _selection = {}; Tri _tri = Tri.recent; bool _tousSelectionnes(List produits) => produits.isNotEmpty && _selection.length == produits.length; void _basculerTout(List produits) { setState(() { if (_tousSelectionnes(produits)) { _selection.clear(); } else { _selection ..clear() ..addAll(produits.map((p) => p.id)); } }); } Future _imprimer(List produits) async { final choisis = produits.where((p) => _selection.contains(p.id)).toList(); if (choisis.isEmpty) return; await Printing.layoutPdf( name: 'etiquettes_prix', onLayout: (format) => EtiquettePdf.construire(choisis), ); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: ListenableBuilder( listenable: _repo, builder: (context, _) { final produits = trierProduits(_repo.produits, _tri); // Nettoie la sélection des produits supprimés. _selection.retainWhere((id) => produits.any((p) => p.id == id)); return Column( children: [ _entete(produits), Expanded( child: produits.isEmpty ? _vide() : ListView.separated( padding: const EdgeInsets.fromLTRB(16, 4, 16, 120), itemCount: produits.length, separatorBuilder: (_, _) => const SizedBox(height: 8), itemBuilder: (_, i) { final p = produits[i]; final sel = _selection.contains(p.id); return _LigneSelection( produit: p, selectionne: sel, onTap: () => setState(() { sel ? _selection.remove(p.id) : _selection.add(p.id); }), ); }, ), ), ], ); }, ), ), floatingActionButton: ListenableBuilder( listenable: _repo, builder: (context, _) { final n = _selection.length; return FloatingActionButton.extended( onPressed: n == 0 ? null : () => _imprimer(trierProduits(_repo.produits, _tri)), backgroundColor: n == 0 ? Colors.grey : AppTheme.accent, foregroundColor: Colors.white, icon: const Icon(Icons.print), label: Text(n == 0 ? 'Imprimer' : 'Imprimer ($n)'), ); }, ), ); } Widget _entete(List produits) { return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Étiquettes', style: TextStyle( fontSize: 30, fontWeight: FontWeight.w800, letterSpacing: -1)), const SizedBox(height: 4), const Text('Sélectionne les produits à imprimer', style: TextStyle(color: AppTheme.grisTexte, fontSize: 14)), const SizedBox(height: 12), Row( children: [ Text('${_selection.length} sélectionné(s)', style: const TextStyle(fontWeight: FontWeight.w600)), const Spacer(), TextButton.icon( onPressed: produits.isEmpty ? null : () => _basculerTout(produits), icon: Icon(_tousSelectionnes(produits) ? Icons.remove_done : Icons.done_all), label: Text(_tousSelectionnes(produits) ? 'Tout désélectionner' : 'Tout sélectionner'), style: TextButton.styleFrom(foregroundColor: AppTheme.accent), ), ], ), const SizedBox(height: 8), SizedBox( height: 36, child: ListView( scrollDirection: Axis.horizontal, children: [for (final t in Tri.values) _puceTri(t)], ), ), ], ), ); } Widget _puceTri(Tri t) { final sel = _tri == t; return Padding( padding: const EdgeInsets.only(right: 8), child: ChoiceChip( label: Text(t.libelle), selected: sel, onSelected: (_) => setState(() => _tri = t), showCheckmark: false, labelStyle: TextStyle( fontWeight: FontWeight.w600, color: sel ? Colors.white : AppTheme.grisTexte, ), selectedColor: AppTheme.accent, backgroundColor: Theme.of(context).colorScheme.surface, side: BorderSide( color: sel ? AppTheme.accent : Theme.of(context).colorScheme.outlineVariant, ), ), ); } Widget _vide() { return const Center( child: Padding( padding: EdgeInsets.all(32), child: Text( 'Ajoute d’abord des produits pour pouvoir imprimer leurs étiquettes.', textAlign: TextAlign.center, style: TextStyle(color: AppTheme.grisTexte, fontSize: 15), ), ), ); } } class _LigneSelection extends StatelessWidget { final Produit produit; final bool selectionne; final VoidCallback onTap; const _LigneSelection({ required this.produit, required this.selectionne, required this.onTap, }); @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Material( color: selectionne ? AppTheme.accent.withValues(alpha: 0.08) : scheme.surface, borderRadius: BorderRadius.circular(16), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(16), child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), border: Border.all( color: selectionne ? AppTheme.accent : scheme.outlineVariant.withValues(alpha: 0.5), width: selectionne ? 1.6 : 1, ), ), child: Row( children: [ _Case(coche: selectionne), const SizedBox(width: 12), ProduitImage(url: produit.imageUrl, taille: 44, radius: 10), const SizedBox(width: 12), Expanded( child: Text(produit.titre, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600)), ), const SizedBox(width: 8), Text(euro(produit.prix), style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w800)), ], ), ), ), ); } } class _Case extends StatelessWidget { final bool coche; const _Case({required this.coche}); @override Widget build(BuildContext context) { return AnimatedContainer( duration: const Duration(milliseconds: 120), width: 26, height: 26, decoration: BoxDecoration( color: coche ? AppTheme.accent : Colors.transparent, borderRadius: BorderRadius.circular(8), border: Border.all( color: coche ? AppTheme.accent : AppTheme.grisTexte, width: 2, ), ), child: coche ? const Icon(Icons.check, size: 18, color: Colors.white) : null, ); } }