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,
),
),
),
),
],
),
),

View File

@@ -30,10 +30,17 @@ class _HomeScreenState extends State<HomeScreen> {
}
Future<void> _loadDefaultDuration() async {
final duration = await SettingsScreen.getDefaultDuration();
setState(() {
_defaultDuration = duration;
});
try {
final duration = await SettingsScreen.getDefaultDuration();
setState(() {
_defaultDuration = duration;
});
} catch (e) {
// Use default duration if loading fails
setState(() {
_defaultDuration = 25;
});
}
}
@override

View File

@@ -11,20 +11,34 @@ class SettingsScreen extends StatefulWidget {
/// Get the saved default duration (for use in other screens)
static Future<int> getDefaultDuration() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(_durationKey) ?? 25;
try {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(_durationKey) ?? 25;
} catch (e) {
// Return default duration if loading fails
return 25;
}
}
/// Get the saved locale
static Future<String?> getSavedLocale() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_localeKey);
try {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_localeKey);
} catch (e) {
// Return null if loading fails
return null;
}
}
/// Save the locale
static Future<void> saveLocale(String localeCode) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_localeKey, localeCode);
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_localeKey, localeCode);
} catch (e) {
// Ignore save errors
}
}
static const String _durationKey = 'default_duration';
@@ -48,36 +62,58 @@ class _SettingsScreenState extends State<SettingsScreen> {
}
Future<void> _loadSavedDuration() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedDuration = prefs.getInt(SettingsScreen._durationKey) ?? 25;
});
try {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedDuration = prefs.getInt(SettingsScreen._durationKey) ?? 25;
});
} catch (e) {
// Use default duration if loading fails
setState(() {
_selectedDuration = 25;
});
}
}
Future<void> _loadSavedLocale() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLocale = prefs.getString(SettingsScreen._localeKey) ?? 'en';
});
try {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLocale = prefs.getString(SettingsScreen._localeKey) ?? 'en';
});
} catch (e) {
// Use default locale if loading fails
setState(() {
_selectedLocale = 'en';
});
}
}
Future<void> _saveDuration(int duration) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(SettingsScreen._durationKey, duration);
setState(() {
_selectedDuration = duration;
});
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(SettingsScreen._durationKey, duration);
setState(() {
_selectedDuration = duration;
});
} catch (e) {
// Ignore save errors, state will be reset on next load
}
}
Future<void> _saveLocale(String localeCode) async {
await SettingsScreen.saveLocale(localeCode);
setState(() {
_selectedLocale = localeCode;
});
try {
await SettingsScreen.saveLocale(localeCode);
setState(() {
_selectedLocale = localeCode;
});
// Update locale immediately without restart
if (!mounted) return;
MyApp.updateLocale(context, localeCode);
// Update locale immediately without restart
if (!mounted) return;
MyApp.updateLocale(context, localeCode);
} catch (e) {
// Ignore save errors
}
}
@override