import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../providers/attendance_provider.dart'; import '../providers/shift_countdown_provider.dart'; /// A persistent banner that shows a countdown to the shift start time. /// /// Activated when [shiftCountdownProvider] is set to a non-null [DateTime]. /// Auto-dismisses when the countdown reaches zero or the provider is cleared /// (e.g., when the user checks in). class ShiftCountdownBanner extends ConsumerStatefulWidget { const ShiftCountdownBanner({required this.child, super.key}); final Widget child; @override ConsumerState createState() => _ShiftCountdownBannerState(); } class _ShiftCountdownBannerState extends ConsumerState { Timer? _timer; Duration _remaining = Duration.zero; @override void dispose() { _timer?.cancel(); super.dispose(); } void _startTimer(DateTime target) { _timer?.cancel(); _updateRemaining(target); _timer = Timer.periodic(const Duration(seconds: 1), (_) { _updateRemaining(target); }); } void _updateRemaining(DateTime target) { final now = DateTime.now(); final diff = target.difference(now); if (diff.isNegative || diff == Duration.zero) { _timer?.cancel(); // Clear the provider when countdown finishes ref.read(shiftCountdownProvider.notifier).state = null; if (mounted) setState(() => _remaining = Duration.zero); return; } if (mounted) setState(() => _remaining = diff); } 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 target = ref.watch(shiftCountdownProvider); // Start/stop timer when target changes ref.listen(shiftCountdownProvider, (previous, next) { if (next != null) { _startTimer(next); } else { _timer?.cancel(); if (mounted) setState(() => _remaining = Duration.zero); } }); // Auto-dismiss when user checks in today (attendance stream updates) ref.listen(attendanceLogsProvider, (previous, next) { if (target == null) return; final logs = next.valueOrNull ?? []; final today = DateTime.now(); final checkedInToday = logs.any( (log) => log.checkInAt.year == today.year && log.checkInAt.month == today.month && log.checkInAt.day == today.day, ); if (checkedInToday) { ref.read(shiftCountdownProvider.notifier).state = null; } }); // Initialize timer on first build if target is already set if (target != null && _timer == null) { WidgetsBinding.instance.addPostFrameCallback((_) { _startTimer(target); }); } final showBanner = target != null && _remaining > Duration.zero; return Column( children: [ if (showBanner) Material( child: InkWell( onTap: () => context.go('/attendance'), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer, ), child: Row( children: [ Icon( Icons.timer_outlined, size: 20, color: Theme.of(context).colorScheme.onPrimaryContainer, ), const SizedBox(width: 8), Expanded( child: Text( 'Shift starts in ${_formatDuration(_remaining)}', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context) .colorScheme .onPrimaryContainer, fontWeight: FontWeight.w600, ), ), ), Text( 'Tap to check in', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context) .colorScheme .onPrimaryContainer .withValues(alpha: 0.7), ), ), const SizedBox(width: 4), Icon( Icons.chevron_right, size: 18, color: Theme.of(context) .colorScheme .onPrimaryContainer .withValues(alpha: 0.7), ), ], ), ), ), ), Expanded(child: widget.child), ], ); } }