import 'dart:async'; import 'package:flutter/material.dart'; import '../theme/app_colors.dart'; import '../theme/app_text_styles.dart'; import '../models/distraction_type.dart'; import '../models/focus_session.dart'; import '../services/storage_service.dart'; import '../services/encouragement_service.dart'; import '../services/notification_service.dart'; import 'complete_screen.dart'; /// Focus Screen - Timer and distraction tracking class FocusScreen extends StatefulWidget { final int durationMinutes; final EncouragementService encouragementService; const FocusScreen({ super.key, required this.durationMinutes, required this.encouragementService, }); @override State createState() => _FocusScreenState(); } class _FocusScreenState extends State { late Timer _timer; late int _remainingSeconds; late DateTime _startTime; final List _distractions = []; bool _isPaused = false; @override void initState() { super.initState(); _remainingSeconds = widget.durationMinutes * 60; _startTime = DateTime.now(); _startTimer(); } void _startTimer() { _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (!_isPaused && _remainingSeconds > 0) { setState(() { _remainingSeconds--; }); if (_remainingSeconds == 0) { _onTimerComplete(); } } }); } void _onTimerComplete() async { _timer.cancel(); _saveFocusSession(completed: true); // Send notification final notificationService = NotificationService(); await notificationService.showFocusCompletedNotification( minutes: widget.durationMinutes, distractionCount: _distractions.length, ); if (!mounted) return; Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => CompleteScreen( focusedMinutes: widget.durationMinutes, distractionCount: _distractions.length, encouragementService: widget.encouragementService, ), ), ); } void _togglePause() { setState(() { _isPaused = !_isPaused; }); } void _stopEarly() { final actualMinutes = ((widget.durationMinutes * 60 - _remainingSeconds) / 60).floor(); showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Stop early?'), content: Text( "That's totally fine — you still focused for $actualMinutes ${actualMinutes == 1 ? 'minute' : 'minutes'}!", style: AppTextStyles.bodyText, ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Keep going'), ), TextButton( onPressed: () { Navigator.pop(context); // Close dialog _timer.cancel(); _saveFocusSession(completed: false); Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => CompleteScreen( focusedMinutes: actualMinutes, distractionCount: _distractions.length, encouragementService: widget.encouragementService, ), ), ); }, child: const Text('Yes, stop'), ), ], ), ); } Future _saveFocusSession({required bool completed}) async { final actualMinutes = completed ? widget.durationMinutes : ((widget.durationMinutes * 60 - _remainingSeconds) / 60).floor(); final session = FocusSession( startTime: _startTime, durationMinutes: widget.durationMinutes, actualMinutes: actualMinutes, distractionCount: _distractions.length, completed: completed, distractionTypes: _distractions, ); final storageService = StorageService(); await storageService.saveFocusSession(session); } void _showDistractionSheet() { showModalBottomSheet( context: context, backgroundColor: AppColors.white, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (context) { return SafeArea( child: SingleChildScrollView( child: Padding( padding: EdgeInsets.only( left: 24.0, right: 24.0, top: 24.0, bottom: 24.0 + MediaQuery.of(context).viewInsets.bottom, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Drag handle Center( child: Container( width: 32, height: 4, decoration: BoxDecoration( color: AppColors.distractionButton, borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 24), // Title const Text( 'What pulled you away?', style: TextStyle( fontFamily: 'Nunito', fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const SizedBox(height: 24), // Distraction options ...DistractionType.all.map((type) { return Column( children: [ ListTile( leading: Text( DistractionType.getEmoji(type), style: const TextStyle(fontSize: 24), ), title: Text( DistractionType.getDisplayName(type), style: AppTextStyles.bodyText, ), onTap: () { Navigator.pop(context); _recordDistraction(type); }, ), if (type != DistractionType.all.last) const Divider(color: AppColors.divider), ], ); }), const SizedBox(height: 16), // Skip button Center( child: TextButton( onPressed: () { Navigator.pop(context); _recordDistraction(null); }, child: const Text('Skip this time'), ), ), ], ), ), ), ); }, ); } void _recordDistraction(String? type) { setState(() { if (type != null) { _distractions.add(type); } }); // Show encouragement toast ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("It happens. Let's gently come back."), duration: Duration(seconds: 2), behavior: SnackBarBehavior.floating, ), ); } String _formatTime(int seconds) { final minutes = seconds ~/ 60; final secs = seconds % 60; return '${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}'; } @override void dispose() { _timer.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, body: SafeArea( child: Column( children: [ Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( children: [ SizedBox( height: MediaQuery.of(context).size.height * 0.2, ), // Timer Display Text( _formatTime(_remainingSeconds), style: AppTextStyles.timerDisplay, ), const SizedBox(height: 80), // "I got distracted" Button SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _showDistractionSheet, style: ElevatedButton.styleFrom( backgroundColor: AppColors.distractionButton, foregroundColor: AppColors.textPrimary, minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'I got distracted', style: TextStyle( fontFamily: 'Nunito', fontSize: 18, fontWeight: FontWeight.w600, ), ), const SizedBox(width: 8), Text( '🤚', style: const TextStyle(fontSize: 20), ), ], ), ), ), const SizedBox(height: 16), // Pause Button SizedBox( width: double.infinity, child: OutlinedButton( onPressed: _togglePause, style: OutlinedButton.styleFrom( foregroundColor: AppColors.primary, side: const BorderSide(color: AppColors.primary, width: 1), minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(_isPaused ? Icons.play_arrow : Icons.pause), const SizedBox(width: 8), Text(_isPaused ? 'Resume' : 'Pause'), ], ), ), ), SizedBox( height: MediaQuery.of(context).size.height * 0.2, ), ], ), ), ), // Stop Button (text button at bottom) Padding( padding: const EdgeInsets.only(bottom: 24.0), child: TextButton( onPressed: _stopEarly, child: const Text( 'Stop session', style: TextStyle( color: AppColors.textSecondary, fontSize: 14, ), ), ), ), ], ), ), ); } }