import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:timetrack/features/entries/domain/entries_provider.dart'; import 'package:timetrack/features/projects/domain/project.dart'; import 'package:timetrack/features/timer/domain/timer_notifier.dart'; import 'package:timetrack/features/timer/domain/timer_state.dart'; import 'package:timetrack/features/timer/presentation/widgets/quick_access_grid.dart'; import 'package:timetrack/features/timer/presentation/widgets/project_picker_chip.dart'; import 'package:timetrack/features/timer/presentation/widgets/timer_controls.dart'; import 'package:timetrack/features/timer/presentation/widgets/timer_display.dart'; import 'package:timetrack/features/timer/presentation/widgets/today_summary_card.dart'; class TimerScreen extends ConsumerStatefulWidget { const TimerScreen({super.key}); @override ConsumerState createState() => _TimerScreenState(); } class _TimerScreenState extends ConsumerState { Project? _selectedProject; final _noteController = TextEditingController(); Timer? _ticker; @override void dispose() { _ticker?.cancel(); _noteController.dispose(); super.dispose(); } void _startTicker() { _ticker?.cancel(); _ticker = Timer.periodic(const Duration(seconds: 1), (_) { if (mounted) setState(() {}); }); } void _stopTicker() { _ticker?.cancel(); _ticker = null; } @override Widget build(BuildContext context) { final timerState = ref.watch(timerNotifierProvider); final isRunning = timerState is TimerRunning; // Start or stop the local ticker based on running state if (isRunning && _ticker == null) { WidgetsBinding.instance.addPostFrameCallback((_) => _startTicker()); } else if (!isRunning && _ticker != null) { _stopTicker(); } // Sync selected project from running state if (isRunning && _selectedProject?.id != timerState.project.id) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) setState(() => _selectedProject = timerState.project); }); } final elapsed = isRunning ? DateTime.now().difference(timerState.startTime) : Duration.zero; return Scaffold( appBar: AppBar(title: const Text('Timer')), body: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 24), const QuickAccessGrid(), const SizedBox(height: 16), ProjectPickerChip( project: isRunning ? timerState.project : _selectedProject, enabled: !isRunning, onChanged: (p) => setState(() => _selectedProject = p), ), const SizedBox(height: 40), TimerDisplay(elapsed: elapsed), const SizedBox(height: 40), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: TextField( controller: _noteController, decoration: const InputDecoration( hintText: 'Add a note…', border: OutlineInputBorder(), prefixIcon: Icon(Icons.notes_outlined), ), maxLines: 1, onChanged: isRunning ? (val) => ref.read(timerNotifierProvider.notifier).updateNote(val) : null, ), ), const SizedBox(height: 40), TimerControls( state: timerState, selectedProject: _selectedProject, ), const SizedBox(height: 32), const TodaySummaryCard(), const SizedBox(height: 16), const _RecentEntriesList(), ], ), ), ); } } class _RecentEntriesList extends ConsumerWidget { const _RecentEntriesList(); @override Widget build(BuildContext context, WidgetRef ref) { final now = DateTime.now(); final todayStart = DateTime(now.year, now.month, now.day); final todayEnd = todayStart.add(const Duration(days: 1)); final entriesAsync = ref.watch( entriesByDateRangeProvider(rangeFrom: todayStart, rangeTo: todayEnd), ); return entriesAsync.when( data: (entries) { final completed = entries.where((e) => e.endTime != null).take(5).toList(); if (completed.isEmpty) return const SizedBox.shrink(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( 'Recent today', style: Theme.of(context).textTheme.labelLarge?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ), ...completed.map( (entry) => ListTile( dense: true, leading: const Icon(Icons.access_time, size: 18), title: Text(entry.note ?? '—'), trailing: Text( _fmt(Duration(seconds: entry.durationSeconds ?? 0)), style: Theme.of(context).textTheme.bodySmall, ), ), ), ], ); }, loading: () => const SizedBox.shrink(), error: (e, st) => const SizedBox.shrink(), ); } String _fmt(Duration d) { final h = d.inHours; final m = d.inMinutes % 60; final s = d.inSeconds % 60; if (h > 0) return '${h}h ${m}m'; if (m > 0) return '${m}m ${s}s'; return '${s}s'; } }