积分、成就系统
This commit is contained in:
@@ -4,6 +4,7 @@ import '../theme/app_colors.dart';
|
||||
import '../theme/app_text_styles.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../services/encouragement_service.dart';
|
||||
import '../models/achievement_config.dart';
|
||||
import 'home_screen.dart';
|
||||
import 'history_screen.dart';
|
||||
|
||||
@@ -11,12 +12,22 @@ import 'history_screen.dart';
|
||||
class CompleteScreen extends StatelessWidget {
|
||||
final int focusedMinutes;
|
||||
final int distractionCount;
|
||||
final int pointsEarned;
|
||||
final int basePoints;
|
||||
final int honestyBonus;
|
||||
final int totalPoints;
|
||||
final List<String> newAchievements;
|
||||
final EncouragementService encouragementService;
|
||||
|
||||
const CompleteScreen({
|
||||
super.key,
|
||||
required this.focusedMinutes,
|
||||
required this.distractionCount,
|
||||
required this.pointsEarned,
|
||||
required this.basePoints,
|
||||
required this.honestyBonus,
|
||||
required this.totalPoints,
|
||||
this.newAchievements = const [],
|
||||
required this.encouragementService,
|
||||
});
|
||||
|
||||
@@ -33,99 +44,413 @@ class CompleteScreen extends StatelessWidget {
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Success Icon
|
||||
const Text(
|
||||
'✨',
|
||||
style: TextStyle(fontSize: 64),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// You focused for X minutes
|
||||
Text(
|
||||
l10n.youFocusedFor,
|
||||
style: AppTextStyles.headline,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.minutesValue(focusedMinutes, l10n.minutes(focusedMinutes)),
|
||||
style: AppTextStyles.largeNumber,
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Stats Card
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
// Success Icon
|
||||
const Text(
|
||||
'✨',
|
||||
style: TextStyle(fontSize: 64),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.totalToday(todayTotal),
|
||||
style: AppTextStyles.bodyText,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
l10n.distractionsCount(todayDistractions, l10n.times(todayDistractions)),
|
||||
style: AppTextStyles.bodyText,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'"$encouragement"',
|
||||
style: AppTextStyles.encouragementQuote,
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// You focused for X minutes
|
||||
Text(
|
||||
l10n.youFocusedFor,
|
||||
style: AppTextStyles.headline,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.minutesValue(focusedMinutes, l10n.minutes(focusedMinutes)),
|
||||
style: AppTextStyles.largeNumber,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Start Another Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
// Points Earned Section
|
||||
_buildPointsCard(context, l10n),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Achievement Unlocked (if any)
|
||||
if (newAchievements.isNotEmpty)
|
||||
..._buildAchievementCards(context, l10n),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Stats Card
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.totalToday(todayTotal),
|
||||
style: AppTextStyles.bodyText,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
l10n.distractionsCount(todayDistractions, l10n.times(todayDistractions)),
|
||||
style: AppTextStyles.bodyText,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'"$encouragement"',
|
||||
style: AppTextStyles.encouragementQuote,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Total Points Display
|
||||
Text(
|
||||
l10n.totalPoints(totalPoints),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Start Another Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => HomeScreen(
|
||||
encouragementService: encouragementService,
|
||||
),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
child: Text(l10n.startAnother),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// View Full Report - Navigate to History
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => HomeScreen(
|
||||
encouragementService: encouragementService,
|
||||
),
|
||||
builder: (context) => const HistoryScreen(),
|
||||
),
|
||||
(route) => false,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
},
|
||||
child: Text(l10n.startAnother),
|
||||
child: Text(l10n.viewHistory),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// View Full Report - Navigate to History
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const HistoryScreen(),
|
||||
),
|
||||
(route) => route.isFirst, // Keep only the home screen in stack
|
||||
);
|
||||
},
|
||||
child: Text(l10n.viewHistory),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build points earned card
|
||||
Widget _buildPointsCard(BuildContext context, AppLocalizations l10n) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppColors.primary.withValues(alpha: 0.3),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Main points display
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.earnedPoints,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 18,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'+$pointsEarned',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
' ⚡',
|
||||
style: TextStyle(fontSize: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
Divider(thickness: 1, color: AppColors.textSecondary.withValues(alpha: 0.2)),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Points breakdown
|
||||
_buildPointRow(
|
||||
l10n.basePoints,
|
||||
'+$basePoints',
|
||||
AppColors.success,
|
||||
),
|
||||
if (honestyBonus > 0) ...[
|
||||
const SizedBox(height: 8),
|
||||
_buildPointRow(
|
||||
l10n.honestyBonus,
|
||||
'+$honestyBonus',
|
||||
AppColors.success,
|
||||
subtitle: l10n.distractionsRecorded(distractionCount, l10n.distractions(distractionCount)),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build a single point row in the breakdown
|
||||
Widget _buildPointRow(
|
||||
String label,
|
||||
String points,
|
||||
Color color, {
|
||||
String? subtitle,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'├─ ',
|
||||
style: TextStyle(
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.4),
|
||||
fontFamily: 'Nunito',
|
||||
),
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
points,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (subtitle != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24, top: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build achievement unlocked cards
|
||||
List<Widget> _buildAchievementCards(BuildContext context, AppLocalizations l10n) {
|
||||
return newAchievements.map((achievementId) {
|
||||
final achievement = AchievementConfig.getById(achievementId);
|
||||
if (achievement == null) return const SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFFFD700), Color(0xFFFFC107)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.orange.withValues(alpha: 0.4),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
achievement.icon,
|
||||
style: const TextStyle(fontSize: 32),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
l10n.achievementUnlocked,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
_getLocalizedAchievementName(l10n, achievement.nameKey),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_getLocalizedAchievementDesc(l10n, achievement.descKey),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (achievement.bonusPoints > 0) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.bonusPoints(achievement.bonusPoints),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Get localized achievement name by key
|
||||
String _getLocalizedAchievementName(AppLocalizations l10n, String key) {
|
||||
switch (key) {
|
||||
case 'achievement_first_session_name':
|
||||
return l10n.achievement_first_session_name;
|
||||
case 'achievement_sessions_10_name':
|
||||
return l10n.achievement_sessions_10_name;
|
||||
case 'achievement_sessions_50_name':
|
||||
return l10n.achievement_sessions_50_name;
|
||||
case 'achievement_sessions_100_name':
|
||||
return l10n.achievement_sessions_100_name;
|
||||
case 'achievement_honest_bronze_name':
|
||||
return l10n.achievement_honest_bronze_name;
|
||||
case 'achievement_honest_silver_name':
|
||||
return l10n.achievement_honest_silver_name;
|
||||
case 'achievement_honest_gold_name':
|
||||
return l10n.achievement_honest_gold_name;
|
||||
case 'achievement_marathon_name':
|
||||
return l10n.achievement_marathon_name;
|
||||
case 'achievement_century_name':
|
||||
return l10n.achievement_century_name;
|
||||
case 'achievement_master_name':
|
||||
return l10n.achievement_master_name;
|
||||
case 'achievement_persistence_star_name':
|
||||
return l10n.achievement_persistence_star_name;
|
||||
case 'achievement_monthly_habit_name':
|
||||
return l10n.achievement_monthly_habit_name;
|
||||
case 'achievement_centurion_name':
|
||||
return l10n.achievement_centurion_name;
|
||||
case 'achievement_year_warrior_name':
|
||||
return l10n.achievement_year_warrior_name;
|
||||
default:
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get localized achievement description by key
|
||||
String _getLocalizedAchievementDesc(AppLocalizations l10n, String key) {
|
||||
switch (key) {
|
||||
case 'achievement_first_session_desc':
|
||||
return l10n.achievement_first_session_desc;
|
||||
case 'achievement_sessions_10_desc':
|
||||
return l10n.achievement_sessions_10_desc;
|
||||
case 'achievement_sessions_50_desc':
|
||||
return l10n.achievement_sessions_50_desc;
|
||||
case 'achievement_sessions_100_desc':
|
||||
return l10n.achievement_sessions_100_desc;
|
||||
case 'achievement_honest_bronze_desc':
|
||||
return l10n.achievement_honest_bronze_desc;
|
||||
case 'achievement_honest_silver_desc':
|
||||
return l10n.achievement_honest_silver_desc;
|
||||
case 'achievement_honest_gold_desc':
|
||||
return l10n.achievement_honest_gold_desc;
|
||||
case 'achievement_marathon_desc':
|
||||
return l10n.achievement_marathon_desc;
|
||||
case 'achievement_century_desc':
|
||||
return l10n.achievement_century_desc;
|
||||
case 'achievement_master_desc':
|
||||
return l10n.achievement_master_desc;
|
||||
case 'achievement_persistence_star_desc':
|
||||
return l10n.achievement_persistence_star_desc;
|
||||
case 'achievement_monthly_habit_desc':
|
||||
return l10n.achievement_monthly_habit_desc;
|
||||
case 'achievement_centurion_desc':
|
||||
return l10n.achievement_centurion_desc;
|
||||
case 'achievement_year_warrior_desc':
|
||||
return l10n.achievement_year_warrior_desc;
|
||||
default:
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user