457 lines
15 KiB
Dart
457 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../l10n/app_localizations.dart';
|
|
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';
|
|
|
|
/// Complete Screen - Shows after focus session ends
|
|
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,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
final storageService = StorageService();
|
|
final todayTotal = storageService.getTodayTotalMinutes();
|
|
final todayDistractions = storageService.getTodayDistractionCount();
|
|
final encouragement = encouragementService.getRandomMessage();
|
|
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
body: SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
|
|
// Success Icon
|
|
const Text(
|
|
'✨',
|
|
style: TextStyle(fontSize: 64),
|
|
),
|
|
|
|
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: 32),
|
|
|
|
// 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) => const HistoryScreen(),
|
|
),
|
|
(route) => route.isFirst,
|
|
);
|
|
},
|
|
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;
|
|
}
|
|
}
|
|
}
|