From 5cb220f68c87442901df55e7b6a1fa05a6808155 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:36:49 -0500 Subject: [PATCH] auto-claude: subtask-3-2 - Extract DeliveryCard to dedicated component file Extracted DeliveryCard widget from deliveries_page.dart to its own component file at lib/components/delivery_card.dart. Also fixed unnecessary non-null assertion warnings by removing redundant '!' operators after AppLocalizations.of(context) calls. Co-Authored-By: Claude --- lib/components/delivery_card.dart | 183 ++++++++++++++++++++++++++++++ lib/pages/deliveries_page.dart | 175 ---------------------------- 2 files changed, 183 insertions(+), 175 deletions(-) create mode 100644 lib/components/delivery_card.dart diff --git a/lib/components/delivery_card.dart b/lib/components/delivery_card.dart new file mode 100644 index 0000000..4503214 --- /dev/null +++ b/lib/components/delivery_card.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import '../models/delivery.dart'; +import '../l10n/app_localizations.dart'; + +/// A card widget that displays delivery information with action buttons. +/// +/// This component shows delivery details including customer name, contact, +/// address, order information, and provides quick action buttons for +/// calling, navigating, and more options. +class DeliveryCard extends StatelessWidget { + final Delivery delivery; + final bool isSelected; + final VoidCallback onTap; + final Function(Delivery, String) onAction; + + const DeliveryCard({ + super.key, + required this.delivery, + this.isSelected = false, + required this.onTap, + required this.onAction, + }); + + @override + Widget build(BuildContext context) { + final contact = delivery.orders.isNotEmpty && delivery.orders.first.contact != null + ? delivery.orders.first.contact + : null; + final order = delivery.orders.isNotEmpty ? delivery.orders.first : null; + + return Card( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + color: isSelected + ? Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3) + : null, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + delivery.name, + style: Theme.of(context).textTheme.titleMedium, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (contact != null) + Text( + contact.fullName, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + if (delivery.delivered) + Chip( + label: Text(AppLocalizations.of(context).delivered), + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + ) + else if (order?.isNewCustomer ?? false) + Chip( + label: Text(AppLocalizations.of(context).newCustomer), + backgroundColor: const Color(0xFFFFFBEB), + ), + ], + ), + const SizedBox(height: 12), + if (delivery.deliveryAddress != null) + Text( + delivery.deliveryAddress!.formattedAddress ?? '', + style: Theme.of(context).textTheme.bodySmall, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (order != null) ...[ + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (order.totalItems != null) + Text( + AppLocalizations.of(context).items(order.totalItems!), + style: Theme.of(context).textTheme.bodySmall, + ), + Text( + AppLocalizations.of(context).moneyCurrency(order.totalAmount), + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ], + const SizedBox(height: 12), + Wrap( + spacing: 8, + children: [ + if (contact?.phoneNumber != null) + OutlinedButton.icon( + onPressed: () => onAction(delivery, 'call'), + icon: const Icon(Icons.phone), + label: Text(AppLocalizations.of(context).call), + ), + if (delivery.deliveryAddress != null) + OutlinedButton.icon( + onPressed: () { + onTap(); // Select the delivery + onAction(delivery, 'map'); + }, + icon: const Icon(Icons.map), + label: Text(AppLocalizations.of(context).navigate), + ), + OutlinedButton.icon( + onPressed: () => _showDeliveryActions(context), + icon: const Icon(Icons.more_vert), + label: Text(AppLocalizations.of(context).more), + ), + ], + ), + ], + ), + ), + ), + ); + } + + void _showDeliveryActions(BuildContext context) { + final l10n = AppLocalizations.of(context); + showModalBottomSheet( + context: context, + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (!delivery.delivered) + ListTile( + leading: const Icon(Icons.check_circle), + title: Text(l10n.markAsCompleted), + onTap: () { + Navigator.pop(context); + onAction(delivery, 'complete'); + }, + ) + else + ListTile( + leading: const Icon(Icons.undo), + title: Text(l10n.markAsUncompleted), + onTap: () { + Navigator.pop(context); + onAction(delivery, 'uncomplete'); + }, + ), + ListTile( + leading: const Icon(Icons.camera_alt), + title: Text(l10n.uploadPhoto), + onTap: () { + Navigator.pop(context); + onAction(delivery, 'photo'); + }, + ), + ListTile( + leading: const Icon(Icons.description), + title: Text(l10n.viewDetails), + onTap: () { + Navigator.pop(context); + onAction(delivery, 'details'); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/deliveries_page.dart b/lib/pages/deliveries_page.dart index 1896ec5..1ee0ef0 100644 --- a/lib/pages/deliveries_page.dart +++ b/lib/pages/deliveries_page.dart @@ -507,178 +507,3 @@ class _DeliveriesPageState extends ConsumerState { } } } - -class DeliveryCard extends StatelessWidget { - final Delivery delivery; - final bool isSelected; - final VoidCallback onTap; - final Function(Delivery, String) onAction; - - const DeliveryCard({ - super.key, - required this.delivery, - this.isSelected = false, - required this.onTap, - required this.onAction, - }); - - @override - Widget build(BuildContext context) { - final contact = delivery.orders.isNotEmpty && delivery.orders.first.contact != null - ? delivery.orders.first.contact - : null; - final order = delivery.orders.isNotEmpty ? delivery.orders.first : null; - - return Card( - margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - color: isSelected - ? Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3) - : null, - child: InkWell( - onTap: onTap, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - delivery.name, - style: Theme.of(context).textTheme.titleMedium, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - if (contact != null) - Text( - contact.fullName, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ), - if (delivery.delivered) - Chip( - label: Text(AppLocalizations.of(context)!.delivered), - backgroundColor: Theme.of(context).colorScheme.primaryContainer, - ) - else if (order?.isNewCustomer ?? false) - Chip( - label: Text(AppLocalizations.of(context)!.newCustomer), - backgroundColor: const Color(0xFFFFFBEB), - ), - ], - ), - const SizedBox(height: 12), - if (delivery.deliveryAddress != null) - Text( - delivery.deliveryAddress!.formattedAddress ?? '', - style: Theme.of(context).textTheme.bodySmall, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - if (order != null) ...[ - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (order.totalItems != null) - Text( - AppLocalizations.of(context)!.items(order.totalItems!), - style: Theme.of(context).textTheme.bodySmall, - ), - Text( - AppLocalizations.of(context)!.moneyCurrency(order.totalAmount), - style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), - ], - ), - ], - const SizedBox(height: 12), - Wrap( - spacing: 8, - children: [ - if (contact?.phoneNumber != null) - OutlinedButton.icon( - onPressed: () => onAction(delivery, 'call'), - icon: const Icon(Icons.phone), - label: Text(AppLocalizations.of(context)!.call), - ), - if (delivery.deliveryAddress != null) - OutlinedButton.icon( - onPressed: () { - onTap(); // Select the delivery - onAction(delivery, 'map'); - }, - icon: const Icon(Icons.map), - label: Text(AppLocalizations.of(context)!.navigate), - ), - OutlinedButton.icon( - onPressed: () => _showDeliveryActions(context), - icon: const Icon(Icons.more_vert), - label: Text(AppLocalizations.of(context)!.more), - ), - ], - ), - ], - ), - ), - ), - ); - } - - void _showDeliveryActions(BuildContext context) { - final l10n = AppLocalizations.of(context)!; - showModalBottomSheet( - context: context, - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (!delivery.delivered) - ListTile( - leading: const Icon(Icons.check_circle), - title: Text(l10n.markAsCompleted), - onTap: () { - Navigator.pop(context); - onAction(delivery, 'complete'); - }, - ) - else - ListTile( - leading: const Icon(Icons.undo), - title: Text(l10n.markAsUncompleted), - onTap: () { - Navigator.pop(context); - onAction(delivery, 'uncomplete'); - }, - ), - ListTile( - leading: const Icon(Icons.camera_alt), - title: Text(l10n.uploadPhoto), - onTap: () { - Navigator.pop(context); - // TODO: Implement photo upload - }, - ), - ListTile( - leading: const Icon(Icons.description), - title: Text(l10n.viewDetails), - onTap: () { - Navigator.pop(context); - // TODO: Navigate to delivery details - }, - ), - ], - ), - ), - ); - } -}