From 5411dfe2c0c0219e02e503dfb8aa00beb9cb7200 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Mon, 4 May 2026 20:50:18 +0200 Subject: [PATCH] feat: add utility functions for date and quantity formatting; refactor inventory and recipe screens to use new formatters --- flutter/lib/core/utils/formatters.dart | 23 +++++++++++++++++++ .../presentation/create_inventory_screen.dart | 6 ++--- .../presentation/inventory_detail_screen.dart | 10 ++------ .../presentation/inventory_edit_screen.dart | 6 ++--- .../swipeable_inventory_tile.dart | 13 +++-------- .../presentation/meal_plan_screen.dart | 6 ++--- .../presentation/create_recipe_screen.dart | 7 ++---- .../presentation/recipe_detail_screen.dart | 4 ++-- .../presentation/recipe_edit_screen.dart | 6 ++--- 9 files changed, 40 insertions(+), 41 deletions(-) create mode 100644 flutter/lib/core/utils/formatters.dart diff --git a/flutter/lib/core/utils/formatters.dart b/flutter/lib/core/utils/formatters.dart new file mode 100644 index 00000000..2e8f1b53 --- /dev/null +++ b/flutter/lib/core/utils/formatters.dart @@ -0,0 +1,23 @@ +/// Formaterar ett DateTime-objekt som YYYY-MM-DD. +/// Om [dt] är null returneras [fallback] (default: tom sträng). +String formatDate(DateTime? dt, {String fallback = ''}) { + if (dt == null) return fallback; + return '${dt.year}-' + '${dt.month.toString().padLeft(2, '0')}-' + '${dt.day.toString().padLeft(2, '0')}'; +} + +/// Parsar en ISO-datumsträng och formaterar som YYYY-MM-DD. +/// Om strängen inte är parsbar returneras den oförändrad. +String formatDateString(String iso) { + try { + return formatDate(DateTime.parse(iso), fallback: iso); + } catch (_) { + return iso; + } +} + +/// Formaterar ett tal som heltal om det inte har decimaler, annars med decimaler. +/// Exempel: 2.0 → "2", 1.5 → "1.5" +String formatQuantity(double v) => + v == v.roundToDouble() ? v.toStringAsFixed(0) : v.toStringAsFixed(1); diff --git a/flutter/lib/features/inventory/presentation/create_inventory_screen.dart b/flutter/lib/features/inventory/presentation/create_inventory_screen.dart index ab1d2478..8d38bf48 100644 --- a/flutter/lib/features/inventory/presentation/create_inventory_screen.dart +++ b/flutter/lib/features/inventory/presentation/create_inventory_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; +import '../../../core/utils/formatters.dart'; import '../../../core/api/api_paths.dart'; import '../../../core/api/api_providers.dart'; import '../../../core/forms/form_options.dart'; @@ -142,10 +143,7 @@ class _CreateInventoryScreenState } } - String _formatDate(DateTime? dt) { - if (dt == null) return context.l10n.selectDateLabel; - return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}'; - } + String _formatDate(DateTime? dt) => formatDate(dt, fallback: context.l10n.selectDateLabel); @override Widget build(BuildContext context) { diff --git a/flutter/lib/features/inventory/presentation/inventory_detail_screen.dart b/flutter/lib/features/inventory/presentation/inventory_detail_screen.dart index ecedd91d..f761dd6e 100644 --- a/flutter/lib/features/inventory/presentation/inventory_detail_screen.dart +++ b/flutter/lib/features/inventory/presentation/inventory_detail_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; +import '../../../core/utils/formatters.dart'; import '../../../core/l10n/l10n.dart'; import '../../../core/ui/async_state_views.dart'; import '../../auth/data/auth_providers.dart'; @@ -77,14 +78,7 @@ class InventoryDetailScreen extends ConsumerWidget { ); } - String _formatDate(String iso) { - try { - final dt = DateTime.parse(iso); - return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}'; - } catch (_) { - return iso; - } - } + String _formatDate(String iso) => formatDateString(iso); } class _DeleteButton extends ConsumerWidget { diff --git a/flutter/lib/features/inventory/presentation/inventory_edit_screen.dart b/flutter/lib/features/inventory/presentation/inventory_edit_screen.dart index cc731d5a..fa64f565 100644 --- a/flutter/lib/features/inventory/presentation/inventory_edit_screen.dart +++ b/flutter/lib/features/inventory/presentation/inventory_edit_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; +import '../../../core/utils/formatters.dart'; import '../../../core/forms/form_options.dart'; import '../../../core/l10n/l10n.dart'; import '../../../core/ui/async_state_views.dart'; @@ -117,10 +118,7 @@ class _InventoryEditScreenState extends ConsumerState { } } - String _formatDate(DateTime? dt) { - if (dt == null) return context.l10n.selectDateLabel; - return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}'; - } + String _formatDate(DateTime? dt) => formatDate(dt, fallback: context.l10n.selectDateLabel); @override Widget build(BuildContext context) { diff --git a/flutter/lib/features/inventory/presentation/swipeable_inventory_tile.dart b/flutter/lib/features/inventory/presentation/swipeable_inventory_tile.dart index f32834d3..1f36c4be 100644 --- a/flutter/lib/features/inventory/presentation/swipeable_inventory_tile.dart +++ b/flutter/lib/features/inventory/presentation/swipeable_inventory_tile.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; +import '../../../core/utils/formatters.dart'; import '../../auth/data/auth_providers.dart'; import '../data/inventory_providers.dart'; import '../domain/inventory_item.dart'; @@ -287,17 +288,9 @@ class _ForegroundTile extends ConsumerWidget { ); } - String _fmtQty(double v) => - v == v.roundToDouble() ? v.toStringAsFixed(0) : v.toStringAsFixed(1); + String _fmtQty(double v) => formatQuantity(v); - String _formatDate(String iso) { - try { - final dt = DateTime.parse(iso); - return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-' - '${dt.day.toString().padLeft(2, '0')}'; - } catch (_) { - return iso; - } + String _formatDate(String iso) => formatDateString(iso); } } diff --git a/flutter/lib/features/meal_plan/presentation/meal_plan_screen.dart b/flutter/lib/features/meal_plan/presentation/meal_plan_screen.dart index 51fe4a2c..c4af3bcb 100644 --- a/flutter/lib/features/meal_plan/presentation/meal_plan_screen.dart +++ b/flutter/lib/features/meal_plan/presentation/meal_plan_screen.dart @@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../../core/api/api_error_mapper.dart'; +import '../../../core/utils/formatters.dart'; import '../../../core/l10n/l10n.dart'; import '../../../core/ui/async_state_views.dart'; import '../../auth/data/auth_providers.dart'; @@ -591,8 +592,5 @@ class _EnrichedShoppingItem { } } - String _formatQuantity(double value) { - final normalized = value == value.roundToDouble() ? value.toStringAsFixed(0) : value.toStringAsFixed(1); - return normalized.replaceAll(RegExp(r'\.0$'), ''); - } + String _formatQuantity(double value) => formatQuantity(value); } \ No newline at end of file diff --git a/flutter/lib/features/recipes/presentation/create_recipe_screen.dart b/flutter/lib/features/recipes/presentation/create_recipe_screen.dart index 3ffa137e..966b318a 100644 --- a/flutter/lib/features/recipes/presentation/create_recipe_screen.dart +++ b/flutter/lib/features/recipes/presentation/create_recipe_screen.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; import '../../../core/api/api_exception.dart'; +import '../../../core/utils/formatters.dart'; import '../../../core/l10n/l10n.dart'; import '../../auth/data/auth_providers.dart'; import '../data/recipe_providers.dart'; @@ -305,11 +306,7 @@ class _CreateRecipeScreenState extends ConsumerState { } Widget _buildIngredientRow(int index, ParsedIngredient ing) { - final qtyStr = ing.quantity > 0 - ? (ing.quantity == ing.quantity.truncateToDouble() - ? '${ing.quantity.toInt()} ' - : '${ing.quantity} ') - : ''; + final qtyStr = ing.quantity > 0 ? '${formatQuantity(ing.quantity)} ' : ''; final unitStr = ing.unit.isNotEmpty ? '${ing.unit} ' : ''; final noteStr = ing.note != null ? ' (${ing.note})' : ''; final label = '$qtyStr$unitStr${ing.rawName}$noteStr'; diff --git a/flutter/lib/features/recipes/presentation/recipe_detail_screen.dart b/flutter/lib/features/recipes/presentation/recipe_detail_screen.dart index fe0bd1c9..6ff69888 100644 --- a/flutter/lib/features/recipes/presentation/recipe_detail_screen.dart +++ b/flutter/lib/features/recipes/presentation/recipe_detail_screen.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; import '../../../core/api/api_exception.dart'; +import '../../../core/utils/formatters.dart'; import '../../../core/auth/jwt_decoder.dart'; import '../../../core/l10n/l10n.dart'; import '../../../core/ui/async_state_views.dart'; @@ -12,8 +13,7 @@ import '../data/recipe_providers.dart'; import '../domain/recipe.dart'; import '../domain/inventory_preview.dart'; -String _fmtQty(double v) => - v == v.truncateToDouble() ? v.toInt().toString() : v.toString(); +String _fmtQty(double v) => formatQuantity(v); enum _ShareAction { share, unshare } diff --git a/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart b/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart index 9fff2a73..a4da00fd 100644 --- a/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart +++ b/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; import '../../../core/api/api_exception.dart'; +import '../../../core/utils/formatters.dart'; import '../../../core/api/api_paths.dart'; import '../../../core/api/api_providers.dart'; import '../../../core/forms/form_options.dart'; @@ -31,13 +32,10 @@ class _EditableIngredient { noteCtrl = TextEditingController(text: note); factory _EditableIngredient.fromRecipe(RecipeIngredient ingredient) { - final quantity = ingredient.quantity == ingredient.quantity.truncateToDouble() - ? ingredient.quantity.toInt().toString() - : ingredient.quantity.toString(); return _EditableIngredient( productId: ingredient.productId, productName: ingredient.productName, - quantity: quantity, + quantity: formatQuantity(ingredient.quantity), unit: ingredient.unit, note: ingredient.note ?? '', );