From 49e7bcdbfae8564fc4bc3e46846a5b8d6d803d02 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Sun, 17 May 2026 23:26:55 +0200 Subject: [PATCH] refactor(meal_planner): estrai logica QR in qr_codec.dart buildQrPayload e parseMealPlanFromQr erano metodi privati non testabili. Estratti in un modulo dedicato e importati da qr_share_sheet e qr_scan_page. Co-Authored-By: Claude Sonnet 4.6 --- .../presentation/pages/qr_scan_page.dart | 58 ++++-------------- .../presentation/widgets/qr_share_sheet.dart | 16 +---- lib/features/meal_planner/qr_codec.dart | 60 +++++++++++++++++++ 3 files changed, 74 insertions(+), 60 deletions(-) create mode 100644 lib/features/meal_planner/qr_codec.dart diff --git a/lib/features/meal_planner/presentation/pages/qr_scan_page.dart b/lib/features/meal_planner/presentation/pages/qr_scan_page.dart index 81629bd..c7470c1 100644 --- a/lib/features/meal_planner/presentation/pages/qr_scan_page.dart +++ b/lib/features/meal_planner/presentation/pages/qr_scan_page.dart @@ -1,10 +1,8 @@ -import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:provider/provider.dart'; -import '../../models/meal_plan.dart'; import '../../providers/meal_planner_provider.dart'; -import '../../../../core/constants/app_constants.dart'; +import '../../qr_codec.dart'; class QrScanPage extends StatefulWidget { const QrScanPage({super.key}); @@ -23,51 +21,19 @@ class _QrScanPageState extends State { setState(() => _handled = true); - try { - final parsed = jsonDecode(raw) as Map; - if (parsed['v'] != 1 || parsed['meals'] is! Map) { - _showError(context, 'QR non valido: dati non riconosciuti.'); - return; - } - final mealsMap = parsed['meals'] as Map; - for (final day in kDayIds) { - final dayData = mealsMap[day]; - if (dayData is! Map) { - _showError(context, 'QR non valido: struttura dati errata.'); - return; - } - for (final slot in kMealSlots) { - if (dayData[slot] is! List) { - _showError(context, 'QR non valido: struttura dati errata.'); - return; - } - } - } + final (plan, error) = parseMealPlanFromQr(raw); + if (error != null) { + _showError(context, error); + return; + } - final plan = MealPlan(Map.fromEntries( - kDayIds.map((day) { - final d = mealsMap[day] as Map; - return MapEntry( - day, - DayPlan( - colazione: List.from(d['colazione'] as List), - pranzo: List.from(d['pranzo'] as List), - cena: List.from(d['cena'] as List), - ), - ); - }), - )); + context.read().importPlan(plan!); - context.read().importPlan(plan); - - if (context.mounted) { - Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Piano ricevuto!')), - ); - } - } catch (_) { - _showError(context, 'QR non riconosciuto.'); + if (context.mounted) { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Piano ricevuto!')), + ); } } diff --git a/lib/features/meal_planner/presentation/widgets/qr_share_sheet.dart b/lib/features/meal_planner/presentation/widgets/qr_share_sheet.dart index 07241d6..c3407f4 100644 --- a/lib/features/meal_planner/presentation/widgets/qr_share_sheet.dart +++ b/lib/features/meal_planner/presentation/widgets/qr_share_sheet.dart @@ -1,9 +1,7 @@ -import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart'; import '../../models/meal_plan.dart'; - -const int _kQrMaxBytes = 2953; +import '../../qr_codec.dart'; void showQrShareSheet(BuildContext context, MealPlan plan) { showModalBottomSheet( @@ -20,17 +18,7 @@ class _QrShareSheet extends StatelessWidget { final MealPlan plan; const _QrShareSheet({required this.plan}); - String? _buildPayload() { - final payload = jsonEncode({'v': 1, 'meals': plan.days.map( - (k, v) => MapEntry(k, { - 'colazione': v.colazione, - 'pranzo': v.pranzo, - 'cena': v.cena, - }), - )}); - if (payload.length > _kQrMaxBytes) return null; - return payload; - } + String? _buildPayload() => buildQrPayload(plan); @override Widget build(BuildContext context) { diff --git a/lib/features/meal_planner/qr_codec.dart b/lib/features/meal_planner/qr_codec.dart new file mode 100644 index 0000000..ac61f6f --- /dev/null +++ b/lib/features/meal_planner/qr_codec.dart @@ -0,0 +1,60 @@ +import 'dart:convert'; +import 'models/meal_plan.dart'; +import '../../core/constants/app_constants.dart'; + +const int kQrMaxBytes = 2953; + +/// Serializza il piano in JSON QR. Restituisce null se supera il limite di byte. +String? buildQrPayload(MealPlan plan) { + final payload = jsonEncode({ + 'v': 1, + 'meals': plan.days.map( + (k, v) => MapEntry(k, { + 'colazione': v.colazione, + 'pranzo': v.pranzo, + 'cena': v.cena, + }), + ), + }); + if (payload.length > kQrMaxBytes) return null; + return payload; +} + +/// Parsa una stringa QR in MealPlan. +/// Restituisce (plan, null) in caso di successo, (null, messaggio) in caso di errore. +(MealPlan?, String?) parseMealPlanFromQr(String raw) { + try { + final parsed = jsonDecode(raw) as Map; + if (parsed['v'] != 1 || parsed['meals'] is! Map) { + return (null, 'QR non valido: dati non riconosciuti.'); + } + final mealsMap = parsed['meals'] as Map; + for (final day in kDayIds) { + final dayData = mealsMap[day]; + if (dayData is! Map) { + return (null, 'QR non valido: struttura dati errata.'); + } + for (final slot in kMealSlots) { + if (dayData[slot] is! List) { + return (null, 'QR non valido: struttura dati errata.'); + } + } + } + final plan = MealPlan(Map.fromEntries( + kDayIds.map((day) { + final d = mealsMap[day] as Map; + return MapEntry( + day, + DayPlan( + colazione: List.from(d['colazione'] as List), + pranzo: List.from(d['pranzo'] as List), + cena: List.from(d['cena'] as List), + ), + ); + }), + )); + return (plan, null); + } catch (_) { + return (null, 'QR non riconosciuto.'); + } +}