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 <noreply@anthropic.com>
This commit is contained in:
Mathias Beaulieu-Duncan 2026-01-20 11:36:49 -05:00
parent fcf7e83f13
commit 5cb220f68c
2 changed files with 183 additions and 175 deletions

View File

@ -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');
},
),
],
),
),
);
}
}

View File

@ -507,178 +507,3 @@ class _DeliveriesPageState extends ConsumerState<DeliveriesPage> {
}
}
}
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
},
),
],
),
),
);
}
}