import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../providers/pass_slip_provider.dart'; /// A persistent banner that shows a countdown to the pass slip 1-hour expiry. /// /// Watches [activePassSlipProvider] for the current user's active pass slip. /// When active, calculates remaining time until `slipStart + 1 hour`. /// Auto-dismisses when the pass slip is completed. /// Shows "EXCEEDED" when time has passed the 1-hour mark. class PassSlipCountdownBanner extends ConsumerStatefulWidget { const PassSlipCountdownBanner({required this.child, super.key}); final Widget child; @override ConsumerState createState() => _PassSlipCountdownBannerState(); } class _PassSlipCountdownBannerState extends ConsumerState { Timer? _timer; Duration _remaining = Duration.zero; bool _exceeded = false; @override void dispose() { _timer?.cancel(); super.dispose(); } void _startTimer(DateTime expiresAt) { _timer?.cancel(); _updateRemaining(expiresAt); _timer = Timer.periodic(const Duration(seconds: 1), (_) { _updateRemaining(expiresAt); }); } void _stopTimer() { _timer?.cancel(); _timer = null; if (mounted) { setState(() { _remaining = Duration.zero; _exceeded = false; }); } } void _updateRemaining(DateTime expiresAt) { final now = DateTime.now(); final diff = expiresAt.difference(now); if (mounted) { setState(() { if (diff.isNegative || diff == Duration.zero) { _remaining = Duration.zero; _exceeded = true; } else { _remaining = diff; _exceeded = false; } }); } } String _formatDuration(Duration d) { final minutes = d.inMinutes.remainder(60).toString().padLeft(2, '0'); final seconds = d.inSeconds.remainder(60).toString().padLeft(2, '0'); if (d.inHours > 0) { return '${d.inHours}:$minutes:$seconds'; } return '$minutes:$seconds'; } @override Widget build(BuildContext context) { final activeSlip = ref.watch(activePassSlipProvider); // Start/stop timer based on active pass slip state if (activeSlip != null && activeSlip.slipStart != null) { final expiresAt = activeSlip.slipStart!.add(const Duration(hours: 1)); if (_timer == null) { WidgetsBinding.instance.addPostFrameCallback((_) { _startTimer(expiresAt); }); } } else if (_timer != null) { WidgetsBinding.instance.addPostFrameCallback((_) { _stopTimer(); }); } final showBanner = activeSlip != null && activeSlip.slipStart != null; if (!showBanner) { return widget.child; } final isUrgent = !_exceeded && _remaining.inMinutes < 5; final bgColor = _exceeded || isUrgent ? Theme.of(context).colorScheme.errorContainer : Theme.of(context).colorScheme.tertiaryContainer; final fgColor = _exceeded || isUrgent ? Theme.of(context).colorScheme.onErrorContainer : Theme.of(context).colorScheme.onTertiaryContainer; final String message; final IconData icon; if (_exceeded) { message = 'Pass slip time EXCEEDED — Please return and complete it'; icon = Icons.warning_amber_rounded; } else { message = 'Pass slip expires in ${_formatDuration(_remaining)}'; icon = Icons.directions_walk_rounded; } return Column( children: [ Material( child: InkWell( onTap: () => context.go('/attendance'), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration(color: bgColor), child: Row( children: [ Icon(icon, size: 20, color: fgColor), const SizedBox(width: 8), Expanded( child: Text( message, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: fgColor, fontWeight: FontWeight.w600, ), ), ), Text( 'Tap to complete', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: fgColor.withValues(alpha: 0.7), ), ), const SizedBox(width: 4), Icon( Icons.chevron_right, size: 18, color: fgColor.withValues(alpha: 0.7), ), ], ), ), ), ), Expanded(child: widget.child), ], ); } }