积分、成就系统
This commit is contained in:
@@ -9,6 +9,8 @@ import '../services/di.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../services/encouragement_service.dart';
|
||||
import '../services/notification_service.dart';
|
||||
import '../services/points_service.dart';
|
||||
import '../services/achievement_service.dart';
|
||||
import '../components/timer_display.dart';
|
||||
import '../components/distraction_button.dart';
|
||||
import '../components/control_buttons.dart';
|
||||
@@ -38,6 +40,8 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
bool _isInBackground = false;
|
||||
final NotificationService _notificationService = getIt<NotificationService>();
|
||||
final StorageService _storageService = getIt<StorageService>();
|
||||
final PointsService _pointsService = getIt<PointsService>();
|
||||
final AchievementService _achievementService = getIt<AchievementService>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -85,7 +89,8 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final minutes = _remainingSeconds ~/ 60;
|
||||
final seconds = _remainingSeconds % 60;
|
||||
final timeStr = '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
final timeStr =
|
||||
'${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
_notificationService.showOngoingFocusNotification(
|
||||
remainingMinutes: minutes,
|
||||
remainingSeconds: seconds,
|
||||
@@ -107,7 +112,8 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final minutes = _remainingSeconds ~/ 60;
|
||||
final seconds = _remainingSeconds % 60;
|
||||
final timeStr = '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
final timeStr =
|
||||
'${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
_notificationService.updateOngoingFocusNotification(
|
||||
remainingMinutes: minutes,
|
||||
remainingSeconds: seconds,
|
||||
@@ -130,7 +136,8 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
// Cancel ongoing notification and show completion notification
|
||||
await _notificationService.cancelOngoingFocusNotification();
|
||||
|
||||
await _saveFocusSession(completed: true);
|
||||
// Calculate points and update user progress
|
||||
final pointsData = await _saveFocusSession(completed: true);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -162,6 +169,11 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
builder: (context) => CompleteScreen(
|
||||
focusedMinutes: widget.durationMinutes,
|
||||
distractionCount: _distractions.length,
|
||||
pointsEarned: pointsData['pointsEarned']!,
|
||||
basePoints: pointsData['basePoints']!,
|
||||
honestyBonus: pointsData['honestyBonus']!,
|
||||
totalPoints: pointsData['totalPoints']!,
|
||||
newAchievements: pointsData['newAchievements'] as List<String>,
|
||||
encouragementService: widget.encouragementService,
|
||||
),
|
||||
),
|
||||
@@ -183,8 +195,11 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
|
||||
void _stopEarly() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final actualMinutes = ((widget.durationMinutes * 60 - _remainingSeconds) / 60).floor();
|
||||
final minuteText = actualMinutes == 1 ? l10n.minutes(1) : l10n.minutes(actualMinutes);
|
||||
final actualMinutes =
|
||||
((widget.durationMinutes * 60 - _remainingSeconds) / 60).floor();
|
||||
final minuteText = actualMinutes == 1
|
||||
? l10n.minutes(1)
|
||||
: l10n.minutes(actualMinutes);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -200,21 +215,36 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
child: Text(l10n.keepGoing),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context); // Close dialog
|
||||
onPressed: () async {
|
||||
// Close dialog immediately
|
||||
Navigator.pop(context);
|
||||
_timer.cancel();
|
||||
_saveFocusSession(completed: false);
|
||||
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CompleteScreen(
|
||||
focusedMinutes: actualMinutes,
|
||||
distractionCount: _distractions.length,
|
||||
encouragementService: widget.encouragementService,
|
||||
),
|
||||
),
|
||||
);
|
||||
// Calculate points and update user progress
|
||||
final pointsData = await _saveFocusSession(completed: false);
|
||||
|
||||
// Create a new context for navigation
|
||||
if (mounted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CompleteScreen(
|
||||
focusedMinutes: actualMinutes,
|
||||
distractionCount: _distractions.length,
|
||||
pointsEarned: pointsData['pointsEarned']!,
|
||||
basePoints: pointsData['basePoints']!,
|
||||
honestyBonus: pointsData['honestyBonus']!,
|
||||
totalPoints: pointsData['totalPoints']!,
|
||||
newAchievements: pointsData['newAchievements'] as List<String>,
|
||||
encouragementService: widget.encouragementService,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Text(l10n.yesStop),
|
||||
),
|
||||
@@ -223,7 +253,9 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveFocusSession({required bool completed}) async {
|
||||
Future<Map<String, dynamic>> _saveFocusSession({
|
||||
required bool completed,
|
||||
}) async {
|
||||
try {
|
||||
final actualMinutes = completed
|
||||
? widget.durationMinutes
|
||||
@@ -238,9 +270,47 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
distractionTypes: _distractions,
|
||||
);
|
||||
|
||||
// Save session
|
||||
await _storageService.saveFocusSession(session);
|
||||
|
||||
// Calculate points
|
||||
final pointsBreakdown = _pointsService.calculateSessionPoints(session);
|
||||
|
||||
// Update user progress
|
||||
final progress = _storageService.getUserProgress();
|
||||
|
||||
// Add points (convert to int explicitly)
|
||||
progress.totalPoints += (pointsBreakdown['total']! as num).toInt();
|
||||
progress.currentPoints += (pointsBreakdown['total']! as num).toInt();
|
||||
|
||||
// Update statistics
|
||||
progress.totalSessions += 1;
|
||||
progress.totalFocusMinutes += actualMinutes;
|
||||
progress.totalDistractions += _distractions.length;
|
||||
|
||||
final newAchievements = await _achievementService.checkAchievementsAsync(
|
||||
progress,
|
||||
);
|
||||
|
||||
// Save updated progress
|
||||
await _storageService.saveUserProgress(progress);
|
||||
|
||||
return {
|
||||
'pointsEarned': pointsBreakdown['total']!,
|
||||
'basePoints': pointsBreakdown['basePoints']!,
|
||||
'honestyBonus': pointsBreakdown['honestyBonus']!,
|
||||
'totalPoints': progress.totalPoints,
|
||||
'newAchievements': newAchievements,
|
||||
};
|
||||
} catch (e) {
|
||||
// Ignore save errors silently
|
||||
// Return default values on error
|
||||
return {
|
||||
'pointsEarned': 0,
|
||||
'basePoints': 0,
|
||||
'honestyBonus': 0,
|
||||
'totalPoints': 0,
|
||||
'newAchievements': <String>[],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +319,10 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
|
||||
// Map distraction types to translations
|
||||
final distractionOptions = [
|
||||
(type: DistractionType.phoneNotification, label: l10n.distractionPhoneNotification),
|
||||
(
|
||||
type: DistractionType.phoneNotification,
|
||||
label: l10n.distractionPhoneNotification,
|
||||
),
|
||||
(type: DistractionType.socialMedia, label: l10n.distractionSocialMedia),
|
||||
(type: DistractionType.thoughts, label: l10n.distractionThoughts),
|
||||
(type: DistractionType.other, label: l10n.distractionOther),
|
||||
@@ -356,7 +429,11 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
// Show distraction-specific encouragement toast
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(widget.encouragementService.getRandomMessage(EncouragementType.distraction)),
|
||||
content: Text(
|
||||
widget.encouragementService.getRandomMessage(
|
||||
EncouragementType.distraction,
|
||||
),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
@@ -377,9 +454,8 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.2,
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.2),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.2),
|
||||
|
||||
// Timer Display Component
|
||||
TimerDisplay(remainingSeconds: _remainingSeconds),
|
||||
@@ -403,10 +479,8 @@ class _FocusScreenState extends State<FocusScreen> with WidgetsBindingObserver {
|
||||
resumeText: l10n.resume,
|
||||
stopText: l10n.stopSession,
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.2,
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.2),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.2),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user