feat: aggrega duplicati nella lista della spesa con quantità
Quando si genera la lista dal piano pasti, gli alimenti ripetuti in più giorni o slot vengono aggregati in un unico elemento con contatore. Es. zucchine lunedì + mercoledì → "zucchine (x2)". - ShoppingItem: aggiunto campo quantity (default 1, persistito in JSON) - ShoppingListProvider.addAll: conta le occorrenze per nome (case-insensitive) e crea un ShoppingItem con quantity = conteggio - ShoppingItemTile: mostra "(xN)" colorato in primary quando quantity > 1, diventa muted insieme al nome quando l'elemento è completato Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,29 +2,34 @@ class ShoppingItem {
|
||||
final String id;
|
||||
final String name;
|
||||
final bool checked;
|
||||
final int quantity;
|
||||
|
||||
const ShoppingItem({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.checked = false,
|
||||
this.quantity = 1,
|
||||
});
|
||||
|
||||
ShoppingItem copyWith({String? id, String? name, bool? checked}) =>
|
||||
ShoppingItem copyWith({String? id, String? name, bool? checked, int? quantity}) =>
|
||||
ShoppingItem(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
checked: checked ?? this.checked,
|
||||
quantity: quantity ?? this.quantity,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'checked': checked,
|
||||
'quantity': quantity,
|
||||
};
|
||||
|
||||
factory ShoppingItem.fromJson(Map<String, dynamic> json) => ShoppingItem(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
checked: json['checked'] as bool? ?? false,
|
||||
quantity: json['quantity'] as int? ?? 1,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,14 +32,33 @@ class ShoppingItemTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: item.checked
|
||||
? theme.textTheme.bodyMedium?.copyWith(
|
||||
decoration: TextDecoration.lineThrough,
|
||||
color: muted,
|
||||
)
|
||||
: theme.textTheme.bodyMedium,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: item.checked
|
||||
? theme.textTheme.bodyMedium?.copyWith(
|
||||
decoration: TextDecoration.lineThrough,
|
||||
color: muted,
|
||||
)
|
||||
: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
if (item.quantity > 1)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: Text(
|
||||
'(x${item.quantity})',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: item.checked
|
||||
? muted
|
||||
: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
|
||||
@@ -49,16 +49,26 @@ class ShoppingListProvider extends ChangeNotifier {
|
||||
|
||||
void addAll(List<String> names) {
|
||||
final existing = _items.map((i) => i.name.toLowerCase()).toSet();
|
||||
final seen = <String>{};
|
||||
final toAdd = <ShoppingItem>[];
|
||||
|
||||
// Conta le occorrenze mantenendo il nome nella sua forma originale (prima occorrenza)
|
||||
final counts = <String, int>{};
|
||||
final canonical = <String, String>{};
|
||||
for (final name in names) {
|
||||
final key = name.toLowerCase().trim();
|
||||
if (key.isNotEmpty && !existing.contains(key) && seen.add(key)) {
|
||||
toAdd.add(ShoppingItem(
|
||||
id: '${DateTime.now().millisecondsSinceEpoch}_${seen.length}',
|
||||
name: name.trim(),
|
||||
));
|
||||
}
|
||||
if (key.isEmpty) continue;
|
||||
counts[key] = (counts[key] ?? 0) + 1;
|
||||
canonical.putIfAbsent(key, () => name.trim());
|
||||
}
|
||||
|
||||
final toAdd = <ShoppingItem>[];
|
||||
var i = 0;
|
||||
for (final entry in counts.entries) {
|
||||
if (existing.contains(entry.key)) continue;
|
||||
toAdd.add(ShoppingItem(
|
||||
id: '${DateTime.now().millisecondsSinceEpoch}_${i++}',
|
||||
name: canonical[entry.key]!,
|
||||
quantity: entry.value,
|
||||
));
|
||||
}
|
||||
if (toAdd.isEmpty) return;
|
||||
_items = [..._items, ...toAdd];
|
||||
|
||||
Reference in New Issue
Block a user