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

259 lines
9.1 KiB
Dart

import 'package:flutter/material.dart';
import '../models/delivery_route.dart';
import '../theme/animation_system.dart';
import '../theme/color_system.dart';
import '../l10n/app_localizations.dart';
class RouteListItem extends StatefulWidget {
final DeliveryRoute route;
final bool isSelected;
final VoidCallback onTap;
final int? animationIndex;
final bool isCollapsed;
const RouteListItem({
super.key,
required this.route,
required this.isSelected,
required this.onTap,
this.animationIndex,
this.isCollapsed = false,
});
@override
State<RouteListItem> createState() => _RouteListItemState();
}
class _RouteListItemState extends State<RouteListItem>
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: 20, 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(DeliveryRoute route) {
if (route.completed) return SvrntyColors.statusCompleted; // Green
if (route.deliveredCount > 0) return SvrntyColors.warning; // Yellow - started but not complete
return SvrntyColors.statusCancelled; // Grey - not started
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final statusColor = _getStatusColor(widget.route);
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: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(10),
boxShadow: (_isHovered || widget.isSelected)
? [
BoxShadow(
color: Colors.black.withValues(
alpha: isDark ? 0.3 : 0.15,
),
blurRadius: 8,
offset: const Offset(0, 4),
),
]
: [],
),
child: Center(
child: Text(
'${(widget.animationIndex ?? 0) + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 26,
fontWeight: FontWeight.w700,
),
),
),
),
),
),
),
),
),
);
}
// 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: 6,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: widget.route.completed
? Colors.green.withValues(alpha: 0.15)
: (_isHovered || widget.isSelected
? Theme.of(context).colorScheme.surfaceContainer
: Colors.transparent),
boxShadow: (_isHovered || widget.isSelected) && !widget.route.completed
? [
BoxShadow(
color: Colors.black.withValues(
alpha: isDark ? 0.3 : 0.08,
),
blurRadius: 8,
offset: const Offset(0, 4),
),
]
: [],
),
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 12),
child: Column(
children: [
// Main route info row
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Route number badge (left of status bar)
Container(
width: 45,
height: 45,
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'${(widget.animationIndex ?? 0) + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
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),
// Route info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Route Name
Text(
widget.route.name,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 16,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
// Route details
Text(
l10n.routeDeliveries(widget.route.deliveredCount, widget.route.deliveriesCount),
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
fontSize: 13,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
],
),
),
),
),
),
),
);
}
}