ionic-planb-logistic-app-fl.../lib/components/delivery_list_item.dart

360 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import '../models/delivery.dart';
import '../theme/animation_system.dart';
import '../theme/color_system.dart';
import '../l10n/app_localizations.dart';
class DeliveryListItem extends StatefulWidget {
final Delivery delivery;
final bool isSelected;
final VoidCallback onTap;
final VoidCallback? onCall;
final Function(String)? onAction;
final int? animationIndex;
final bool isCollapsed;
const DeliveryListItem({
super.key,
required this.delivery,
required this.isSelected,
required this.onTap,
this.onCall,
this.onAction,
this.animationIndex,
this.isCollapsed = false,
});
@override
State<DeliveryListItem> createState() => _DeliveryListItemState();
}
class _DeliveryListItemState extends State<DeliveryListItem>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _slideAnimation;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
bool _isHovered = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
);
final staggerDelay = Duration(
milliseconds:
(widget.animationIndex ?? 0) * AppAnimations.staggerDelayMs,
);
Future.delayed(staggerDelay, () {
if (mounted) {
_controller.forward();
}
});
_slideAnimation = Tween<double>(begin: 0, end: 0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
_scaleAnimation = Tween<double>(begin: 0.95, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Color _getStatusColor(Delivery delivery) {
// If delivered, always show green (even if selected)
if (delivery.delivered == true) return SvrntyColors.success;
// If selected and not delivered, show yellow/warning color
if (widget.isSelected) return SvrntyColors.warning;
// If skipped, show grey
if (delivery.isSkipped == true) return SvrntyColors.statusCancelled;
// Default: in-transit or pending deliveries
return SvrntyColors.statusInTransit;
}
bool _hasNote() {
return widget.delivery.orders.any((order) =>
order.note != null && order.note!.isNotEmpty
);
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final statusColor = _getStatusColor(widget.delivery);
final l10n = AppLocalizations.of(context)!;
// Collapsed view: Show only the badge
if (widget.isCollapsed) {
return ScaleTransition(
scale: _scaleAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: GestureDetector(
onTap: widget.onTap,
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
),
child: Center(
child: Stack(
clipBehavior: Clip.none,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(10),
border: widget.isSelected
? Border.all(
color: Colors.white,
width: 3,
)
: null,
boxShadow: (_isHovered || widget.isSelected)
? [
BoxShadow(
color: widget.isSelected
? statusColor.withValues(alpha: 0.5)
: Colors.black.withValues(
alpha: isDark ? 0.3 : 0.15,
),
blurRadius: widget.isSelected ? 12 : 8,
offset: const Offset(0, 4),
spreadRadius: widget.isSelected ? 2 : 0,
),
]
: [],
),
child: Center(
child: Text(
'${widget.delivery.deliveryIndex + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 26,
fontWeight: FontWeight.w700,
),
),
),
),
if (_hasNote())
Positioned(
top: -4,
right: -4,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
shape: BoxShape.circle,
border: Border.all(
color: Theme.of(context).colorScheme.surface,
width: 2,
),
),
child: Transform.rotate(
angle: 4.71239, // 270 degrees in radians (3*pi/2)
child: const Icon(
Icons.note,
size: 12,
color: Colors.white,
),
),
),
),
],
),
),
),
),
),
),
);
}
// Expanded view: Show full layout
return ScaleTransition(
scale: _scaleAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: Transform.translate(
offset: Offset(_slideAnimation.value, 0),
child: MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: GestureDetector(
onTap: widget.onTap,
child: AnimatedContainer(
duration: AppAnimations.durationFast,
margin: const EdgeInsets.symmetric(
horizontal: 2,
vertical: 4,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: widget.delivery.delivered
? Colors.green.withValues(alpha: 0.15)
: widget.isSelected
? statusColor.withValues(alpha: 0.15)
: Theme.of(context).colorScheme.surfaceContainer,
border: widget.isSelected
? Border.all(
color: widget.delivery.delivered
? SvrntyColors.success
: statusColor,
width: 2,
)
: null,
boxShadow: (_isHovered || widget.isSelected) && !widget.delivery.delivered
? [
BoxShadow(
color: Colors.black.withValues(
alpha: isDark ? 0.3 : 0.08,
),
blurRadius: 8,
offset: const Offset(0, 4),
),
]
: [],
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
child: Stack(
clipBehavior: Clip.none,
children: [
Column(
children: [
// Main delivery info row
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Order number badge (left of status bar)
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'${widget.delivery.deliveryIndex + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
),
),
const SizedBox(width: 8),
// Left accent bar (vertical status bar)
Container(
width: 4,
height: 50,
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 10),
// Delivery info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Customer Name
Text(
widget.delivery.name,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 16,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
// Address
Text(
widget.delivery.deliveryAddress
?.formattedAddress ??
l10n.noAddress,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
fontSize: 13,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
],
),
if (_hasNote())
Positioned(
top: -8,
right: -4,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
shape: BoxShape.circle,
border: Border.all(
color: Theme.of(context).colorScheme.surface,
width: 2,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Transform.rotate(
angle: 4.71239, // 270 degrees in radians (3*pi/2)
child: const Icon(
Icons.note,
size: 14,
color: Colors.white,
),
),
),
),
],
),
),
),
),
),
),
);
}
}