This commit is contained in:
ytc1012
2025-11-26 16:32:47 +08:00
parent 96658339e1
commit 0195cdf54b
18 changed files with 1052 additions and 240 deletions

View File

@@ -5,9 +5,13 @@ import '../theme/app_colors.dart';
import '../theme/app_text_styles.dart';
import '../models/distraction_type.dart';
import '../models/focus_session.dart';
import '../services/di.dart';
import '../services/storage_service.dart';
import '../services/encouragement_service.dart';
import '../services/notification_service.dart';
import '../components/timer_display.dart';
import '../components/distraction_button.dart';
import '../components/control_buttons.dart';
import 'complete_screen.dart';
/// Focus Screen - Timer and distraction tracking
@@ -32,7 +36,8 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
final List<String> _distractions = [];
bool _isPaused = false;
bool _isInBackground = false;
final NotificationService _notificationService = NotificationService();
final NotificationService _notificationService = getIt<NotificationService>();
final StorageService _storageService = getIt<StorageService>();
@override
void initState() {
@@ -125,7 +130,7 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
// Cancel ongoing notification and show completion notification
await _notificationService.cancelOngoingFocusNotification();
_saveFocusSession(completed: true);
await _saveFocusSession(completed: true);
if (!mounted) return;
@@ -219,21 +224,24 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
}
Future<void> _saveFocusSession({required bool completed}) async {
final actualMinutes = completed
? widget.durationMinutes
: ((widget.durationMinutes * 60 - _remainingSeconds) / 60).floor();
try {
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 session = FocusSession(
startTime: _startTime,
durationMinutes: widget.durationMinutes,
actualMinutes: actualMinutes,
distractionCount: _distractions.length,
completed: completed,
distractionTypes: _distractions,
);
final storageService = StorageService();
await storageService.saveFocusSession(session);
await _storageService.saveFocusSession(session);
} catch (e) {
// Ignore save errors silently
}
}
void _showDistractionSheet() {
@@ -339,30 +347,22 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
}
void _recordDistraction(String? type) {
final l10n = AppLocalizations.of(context)!;
setState(() {
if (type != null) {
_distractions.add(type);
}
});
// Show encouragement toast
// Show distraction-specific encouragement toast
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.distractionEncouragement),
content: Text(widget.encouragementService.getRandomMessage(EncouragementType.distraction)),
duration: const 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
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
@@ -381,73 +381,27 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
height: MediaQuery.of(context).size.height * 0.2,
),
// Timer Display
Text(
_formatTime(_remainingSeconds),
style: AppTextStyles.timerDisplay,
),
// Timer Display Component
TimerDisplay(remainingSeconds: _remainingSeconds),
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: [
Text(
l10n.iGotDistracted,
style: const TextStyle(
fontFamily: 'Nunito',
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 8),
const Text(
'🤚',
style: TextStyle(fontSize: 20),
),
],
),
),
// "I got distracted" Button Component
DistractionButton(
onPressed: _showDistractionSheet,
buttonText: l10n.iGotDistracted,
),
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 ? l10n.resume : l10n.pause),
],
),
),
// Control Buttons Component
ControlButtons(
isPaused: _isPaused,
onTogglePause: _togglePause,
onStopEarly: _stopEarly,
pauseText: l10n.pause,
resumeText: l10n.resume,
stopText: l10n.stopSession,
),
SizedBox(
@@ -457,21 +411,6 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
),
),
),
// Stop Button (text button at bottom)
Padding(
padding: const EdgeInsets.only(bottom: 24.0),
child: TextButton(
onPressed: _stopEarly,
child: Text(
l10n.stopSession,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: 14,
),
),
),
),
],
),
),