import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../providers/cart_controller.dart'; /// Brief floating confirmation after a scan. /// /// Deliberately not a dialog: the cashier must never have to dismiss anything /// between items. class ScanToast extends ConsumerStatefulWidget { const ScanToast({super.key}); @override ConsumerState createState() => _ScanToastState(); } class _ScanToastState extends ConsumerState { Timer? _timer; ScanFeedback? _visible; void _show(ScanFeedback feedback) { _timer?.cancel(); setState(() => _visible = feedback); _timer = Timer( const Duration(milliseconds: 1600), () { if (mounted) setState(() => _visible = null); }, ); } @override void dispose() { _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { ref.listen(scanFeedbackProvider, (prev, next) { if (next != null) _show(next); }); final feedback = _visible; if (feedback == null) return const SizedBox.shrink(); final success = feedback.isSuccess; final color = success ? AppColors.success : AppColors.danger; final product = feedback.product; final message = feedback.message ?? switch (feedback.outcome) { ScanOutcome.added => 'Added to bill', ScanOutcome.incremented => 'Quantity updated', ScanOutcome.notFound => 'Product not found', ScanOutcome.outOfStock => 'Out of stock', }; return Container( margin: const EdgeInsets.symmetric(horizontal: AppSpacing.xxl), padding: const EdgeInsets.symmetric( horizontal: AppSpacing.xl, vertical: AppSpacing.md, ), decoration: const BoxDecoration( color: AppColors.textPrimary, borderRadius: AppRadius.brPill, boxShadow: AppColors.shadowLg, ), child: Row(mainAxisSize: MainAxisSize.min, children: [ Container( width: 26, height: 26, decoration: BoxDecoration(color: color, shape: BoxShape.circle), child: Icon( success ? Icons.check_rounded : Icons.priority_high_rounded, size: 17, color: Colors.white, ), ), const SizedBox(width: AppSpacing.md), if (product != null) ...[ Text(product.emoji, style: const TextStyle(fontSize: 17)), const SizedBox(width: AppSpacing.sm), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 220), child: Text( product.name, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 14.5, ), ), ), const SizedBox(width: AppSpacing.sm), Container(width: 1, height: 16, color: Colors.white24), const SizedBox(width: AppSpacing.sm), ], Text( message, style: TextStyle( color: Colors.white.withValues(alpha: 0.85), fontSize: 13.5, ), ), ],), ) .animate(key: ValueKey(feedback.stamp)) .fadeIn(duration: 140.ms) .slideY(begin: 0.4, end: 0, curve: Curves.easeOutBack) .then(delay: 1200.ms) .fadeOut(duration: 250.ms); } }