import 'package:flutter/material.dart'; import '../l10n/app_localizations.dart'; import '../theme/app_colors.dart'; import '../theme/app_text_styles.dart'; import '../models/focus_session.dart'; import '../models/achievement_config.dart'; import '../services/points_service.dart'; import '../services/storage_service.dart'; import '../services/encouragement_service.dart'; import '../services/di.dart'; /// Session Detail Screen - Shows detailed information about a past focus session class SessionDetailScreen extends StatelessWidget { final FocusSession session; const SessionDetailScreen({super.key, required this.session}); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final pointsService = getIt(); final storageService = getIt(); final encouragementService = getIt(); // Calculate points for this session final pointsBreakdown = pointsService.calculateSessionPoints(session); final pointsEarned = pointsBreakdown['total'] as int; final basePoints = pointsBreakdown['basePoints'] as int; final honestyBonus = pointsBreakdown['honestyBonus'] as int; // Get user progress to show total points final progress = storageService.getUserProgress(); final encouragement = encouragementService.getRandomMessage(); // Find achievements that might have been unlocked during this session final sessionAchievements = _findSessionAchievements( session, storageService, ); return Scaffold( backgroundColor: AppColors.background, appBar: AppBar( title: Text(l10n.history), backgroundColor: AppColors.background, ), body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Session Date and Time _buildSessionHeader(context, l10n), const SizedBox(height: 18), // Focused Time Section Text(l10n.youFocusedFor, style: AppTextStyles.headline), const SizedBox(height: 8), Text( l10n.minutesValue( session.actualMinutes, l10n.minutes(session.actualMinutes), ), style: AppTextStyles.largeNumber, ), const SizedBox(height: 24), // Points Earned Section _buildPointsCard( context, l10n, pointsEarned, basePoints, honestyBonus, ), const SizedBox(height: 16), // Session 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.history, style: AppTextStyles.headline), const SizedBox(height: 16), _buildStatRow( icon: '⏱️', label: l10n.defaultFocusDuration, value: l10n.minutesValue( session.durationMinutes, l10n.minutes(session.durationMinutes), ), ), const SizedBox(height: 12), _buildStatRow( icon: '✅', label: l10n.youFocusedFor, value: l10n.minutesValue( session.actualMinutes, l10n.minutes(session.actualMinutes), ), ), const SizedBox(height: 12), _buildStatRow( icon: '🤚', label: 'Distractions', value: '${session.distractionCount} ${l10n.times(session.distractionCount)}', ), const SizedBox(height: 12), _buildStatRow( icon: '🏁', label: 'Status', value: session.completed ? l10n.completed : l10n.stoppedEarly, ), const SizedBox(height: 20), Text( '"$encouragement"', style: AppTextStyles.encouragementQuote, ), ], ), ), const SizedBox(height: 16), // Achievements Unlocked Section if (sessionAchievements.isNotEmpty) ..._buildAchievementCards(context, l10n, sessionAchievements), const SizedBox(height: 24), // Total Points Display Text( l10n.totalPoints(progress.totalPoints), style: const TextStyle( fontFamily: 'Nunito', fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.primary, ), ), const SizedBox(height: 40), ], ), ), ), ); } /// Build session header with date and time Widget _buildSessionHeader(BuildContext context, AppLocalizations l10n) { final dateStr = session.startTime.toLocal().toString().split(' ')[0]; final timeStr = session.startTime .toLocal() .toString() .split(' ')[1] .substring(0, 5); return Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( dateStr, style: const TextStyle( fontFamily: 'Nunito', fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), const SizedBox(width: 12), const Text('•', style: TextStyle(color: AppColors.textSecondary)), const SizedBox(width: 12), Text( timeStr, style: const TextStyle( fontFamily: 'Nunito', fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), ], ), ); } /// Build points earned card Widget _buildPointsCard( BuildContext context, AppLocalizations l10n, int pointsEarned, int basePoints, int honestyBonus, ) { 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( session.distractionCount, l10n.distractions(session.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 a single stat row Widget _buildStatRow({ required String icon, required String label, required String value, }) { return Row( children: [ Text(icon, style: const TextStyle(fontSize: 20)), const SizedBox(width: 12), Expanded( child: Text( label, style: AppTextStyles.bodyText, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), const SizedBox(width: 8), Text( value, style: const TextStyle( fontFamily: 'Nunito', fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), ], ); } /// Find achievements that might have been unlocked during this session List _findSessionAchievements( FocusSession session, StorageService storageService, ) { final allAchievements = AchievementConfig.all; final unlockedAchievements = storageService .getUserProgress() .unlockedAchievements; final sessionAchievements = []; // Get all sessions to determine the state before this one final allSessions = storageService.getAllSessions(); final sessionIndex = allSessions.indexOf(session); // Calculate stats before this session int sessionsBefore = sessionIndex; int distractionsBefore = allSessions .sublist(0, sessionIndex) .fold(0, (sum, s) => sum + s.distractionCount); int minutesBefore = allSessions .sublist(0, sessionIndex) .fold(0, (sum, s) => sum + s.actualMinutes); // Check which achievements might have been unlocked by this session for (final achievement in allAchievements) { // Skip if not unlocked if (!unlockedAchievements.containsKey(achievement.id)) { continue; } // Check if this session could have unlocked the achievement bool unlockedByThisSession = false; switch (achievement.type) { case AchievementType.sessionCount: unlockedByThisSession = sessionsBefore < achievement.requiredValue && (sessionsBefore + 1) >= achievement.requiredValue; break; case AchievementType.distractionCount: unlockedByThisSession = distractionsBefore < achievement.requiredValue && (distractionsBefore + session.distractionCount) >= achievement.requiredValue; break; case AchievementType.totalMinutes: unlockedByThisSession = minutesBefore < achievement.requiredValue && (minutesBefore + session.actualMinutes) >= achievement.requiredValue; break; case AchievementType.consecutiveDays: // Consecutive days are not directly related to a single session // but rather to check-ins, so we'll skip this type break; } if (unlockedByThisSession) { sessionAchievements.add(achievement); } } return sessionAchievements; } /// Build achievement cards for achievements unlocked in this session List _buildAchievementCards( BuildContext context, AppLocalizations l10n, List achievements, ) { return [ Text(l10n.achievements, style: AppTextStyles.headline), const SizedBox(height: 16), ...achievements.map((achievement) { 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) Padding( padding: const EdgeInsets.only(top: 8), child: Text( '+${achievement.bonusPoints} 积分', style: const TextStyle( fontFamily: 'Nunito', fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ], ), ); }), ]; } /// 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; } } }