import 'package:flutter/material.dart'; import '../l10n/app_localizations.dart'; import '../theme/app_colors.dart'; import '../theme/app_constants.dart'; import '../services/storage_service.dart'; import '../services/points_service.dart'; import '../services/achievement_service.dart'; import '../services/di.dart'; import '../models/user_progress.dart'; import '../models/achievement_config.dart'; /// Profile Screen - Shows user points, level, check-in calendar, and achievements class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key}); @override State createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { final StorageService _storageService = getIt(); final PointsService _pointsService = getIt(); final AchievementService _achievementService = getIt(); late UserProgress _progress; @override void initState() { super.initState(); _progress = _storageService.getUserProgress(); } Future _handleCheckIn() async { final l10n = AppLocalizations.of(context)!; if (_progress.hasCheckedInToday) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(l10n.alreadyCheckedIn), duration: const Duration(seconds: 2), ), ); return; } // Process check-in with detailed breakdown final checkInResult = _pointsService.processCheckIn(_progress); final pointsEarned = checkInResult['points'] as int; // Add points _progress.totalPoints += pointsEarned; _progress.currentPoints += pointsEarned; // Check for newly unlocked achievements (streak achievements) asynchronously final newAchievements = await _achievementService.checkAchievementsAsync( _progress, ); // Save progress await _storageService.saveUserProgress(_progress); // Update UI setState(() {}); // Show success message if (!mounted) return; String message = l10n.checkInSuccess(pointsEarned); if (_progress.consecutiveCheckIns % 7 == 0) { message += '\n${l10n.weeklyStreakBonus}'; } if (newAchievements.isNotEmpty) { message += '\n${l10n.newAchievementUnlocked}'; } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), duration: const Duration(seconds: 3), backgroundColor: AppColors.success, ), ); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Scaffold( backgroundColor: AppColors.background, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, title: Text( l10n.profile, style: const TextStyle( fontFamily: 'Nunito', fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), centerTitle: true, ), body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( children: [ // User header card _buildUserHeaderCard(l10n), const SizedBox(height: 24), // Check-in calendar _buildCheckInCalendar(l10n), const SizedBox(height: 24), // Achievement wall _buildAchievementWall(l10n), const SizedBox(height: 24), ], ), ), ), ); } /// Build user header card with points, level, and progress bar Widget _buildUserHeaderCard(AppLocalizations l10n) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.primary, AppColors.primary.withValues(alpha: 0.8)], ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppColors.primary.withValues(alpha: 0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Column( children: [ // User icon (placeholder) const CircleAvatar( radius: 40, backgroundColor: Colors.white, child: Text('👤', style: TextStyle(fontSize: 40)), ), const SizedBox(height: 16), // User name (placeholder) Text( l10n.focuser, style: const TextStyle( fontFamily: 'Nunito', fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 20), // Points and Level row Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // Points Column( children: [ Row( children: [ const Text('⚡', style: TextStyle(fontSize: 28)), const SizedBox(width: 4), Text( '${_progress.totalPoints}', style: const TextStyle( fontFamily: 'Nunito', fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), Text( l10n.points, style: const TextStyle( fontFamily: 'Nunito', fontSize: 14, color: Colors.white, ), ), ], ), // Divider Container( height: 40, width: 1, color: Colors.white.withValues(alpha: 0.3), ), // Level Column( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 4, ), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(16), ), child: Row( children: [ const Text('🎖️', style: TextStyle(fontSize: 24)), const SizedBox(width: 4), Text( 'Lv ${_progress.level}', style: const TextStyle( fontFamily: 'Nunito', fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), ), const SizedBox(height: 4), Text( l10n.level, style: const TextStyle( fontFamily: 'Nunito', fontSize: 14, color: Colors.white, ), ), ], ), ], ), const SizedBox(height: 20), // Level progress bar Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.pointsToNextLevel( _progress.pointsToNextLevel, _progress.level + 1, ), style: TextStyle( fontFamily: 'Nunito', fontSize: 12, color: Colors.white.withValues(alpha: 0.9), ), ), const SizedBox(height: 8), Stack( children: [ // Background bar Container( height: 10, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(5), ), ), // Progress bar FractionallySizedBox( widthFactor: _progress.levelProgress, child: Container( height: 10, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), ), ), ), ], ), const SizedBox(height: 4), Align( alignment: Alignment.centerRight, child: Text( '${(_progress.levelProgress * 100).toInt()}%', style: TextStyle( fontFamily: 'Nunito', fontSize: 12, color: Colors.white.withValues(alpha: 0.9), ), ), ), ], ), ], ), ); } /// Build check-in calendar section Widget _buildCheckInCalendar(AppLocalizations l10n) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.checkInCalendar, style: const TextStyle( fontFamily: 'Nunito', fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), Text( l10n.daysCount(_progress.checkInHistory.length), style: const TextStyle( fontFamily: 'Nunito', fontSize: 14, color: AppColors.textSecondary, ), ), ], ), const SizedBox(height: 16), // Check-in button if (!_progress.hasCheckedInToday) SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _handleCheckIn, style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, padding: const EdgeInsets.symmetric(vertical: 12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( l10n.checkInToday, style: const TextStyle( fontFamily: 'Nunito', fontSize: 16, fontWeight: FontWeight.w600, ), ), ], ), ), ), if (_progress.hasCheckedInToday) Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: AppColors.success.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( l10n.checkedInToday, style: const TextStyle( fontFamily: 'Nunito', fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.success, ), ), ], ), ), const SizedBox(height: 16), // Stats row Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildStatItem( l10n.currentStreak, l10n.daysCount(_progress.consecutiveCheckIns), ), Container(height: 40, width: 1, color: AppColors.divider), _buildStatItem( l10n.longestStreak, l10n.daysCount(_progress.longestCheckInStreak), ), ], ), const SizedBox(height: 16), // Calendar grid (last 28 days) _buildCalendarGrid(l10n), ], ), ); } /// Build calendar grid showing check-in history Widget _buildCalendarGrid(AppLocalizations l10n) { final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); return Column( children: [ // Weekday labels Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ l10n.weekdayS, l10n.weekdayM, l10n.weekdayT, l10n.weekdayW, l10n.weekdayTh, l10n.weekdayF, l10n.weekdaySa, ] .map( (day) => SizedBox( width: ProfileConstants.calendarCellSize, child: Center( child: Text( day, style: TextStyle( fontFamily: 'Nunito', fontSize: FontSizes.caption, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), ), ), ) .toList(), ), const SizedBox(height: 8), // Calendar days (last 4 weeks) Wrap( spacing: 4, runSpacing: 4, children: List.generate(28, (index) { final date = today.subtract(Duration(days: 27 - index)); final isCheckedIn = _progress.checkInHistory.any( (checkInDate) => checkInDate.year == date.year && checkInDate.month == date.month && checkInDate.day == date.day, ); final isToday = date == today; return Container( width: 40, height: 40, decoration: BoxDecoration( color: isCheckedIn ? AppColors.primary.withValues(alpha: 0.2) : Colors.transparent, borderRadius: BorderRadius.circular(8), border: isToday ? Border.all(color: AppColors.primary, width: 2) : null, ), child: Center( child: Text( isCheckedIn ? '✓' : date.day.toString(), style: TextStyle( fontFamily: 'Nunito', fontSize: 14, fontWeight: FontWeight.w600, color: isCheckedIn ? AppColors.primary : AppColors.textSecondary, ), ), ), ); }), ), ], ); } /// Build a stat item Widget _buildStatItem(String label, String value) { return Column( children: [ Text( value, style: const TextStyle( fontFamily: 'Nunito', fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), const SizedBox(height: 4), Text( label, style: const TextStyle( fontFamily: 'Nunito', fontSize: 12, color: AppColors.textSecondary, ), textAlign: TextAlign.center, ), ], ); } /// Build achievement wall section Widget _buildAchievementWall(AppLocalizations l10n) { final allAchievements = AchievementConfig.all; final unlockedCount = _progress.unlockedAchievements.length; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.achievements, style: const TextStyle( fontFamily: 'Nunito', fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), Text( '$unlockedCount/${allAchievements.length}', style: const TextStyle( fontFamily: 'Nunito', fontSize: 14, color: AppColors.textSecondary, ), ), ], ), const SizedBox(height: 16), // Achievement list ...allAchievements.take(6).map((achievement) { final isUnlocked = _progress.unlockedAchievements.containsKey( achievement.id, ); final progress = _achievementService.getAchievementProgress( _progress, achievement, ); final currentValue = _achievementService.getAchievementCurrentValue( _progress, achievement, ); return _buildAchievementItem( l10n: l10n, achievement: achievement, isUnlocked: isUnlocked, progress: progress, currentValue: currentValue, ); }), const SizedBox(height: 16), // View all button Center( child: TextButton( onPressed: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(l10n.allAchievementsComingSoon), duration: const Duration(seconds: 2), ), ); }, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( l10n.viewAllAchievements, style: const TextStyle( fontFamily: 'Nunito', fontSize: 14, fontWeight: FontWeight.w600, ), ), const SizedBox(width: 4), const Icon(Icons.arrow_forward, size: 16), ], ), ), ), ], ), ); } /// Build a single achievement item Widget _buildAchievementItem({ required AppLocalizations l10n, required AchievementConfig achievement, required bool isUnlocked, required double progress, required int currentValue, }) { return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: isUnlocked ? AppColors.success.withValues(alpha: 0.05) : AppColors.background, borderRadius: BorderRadius.circular(12), border: Border.all( color: isUnlocked ? AppColors.success.withValues(alpha: 0.3) : AppColors.divider, width: 1, ), ), child: Row( children: [ // Icon Container( width: 48, height: 48, decoration: BoxDecoration( color: isUnlocked ? AppColors.success.withValues(alpha: 0.1) : Colors.grey.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( achievement.icon, style: TextStyle( fontSize: 24, color: isUnlocked ? null : Colors.grey, ), ), ), ), const SizedBox(width: 12), // Content Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _getLocalizedAchievementName(l10n, achievement.nameKey), style: TextStyle( fontFamily: 'Nunito', fontSize: 14, fontWeight: FontWeight.w600, color: isUnlocked ? AppColors.textPrimary : AppColors.textSecondary, ), ), const SizedBox(height: 4), Text( _getLocalizedAchievementDesc(l10n, achievement.descKey), style: TextStyle( fontFamily: 'Nunito', fontSize: 12, color: AppColors.textSecondary.withValues(alpha: 0.8), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), if (!isUnlocked) ...[ const SizedBox(height: 8), Row( children: [ Expanded( child: LinearProgressIndicator( value: progress, backgroundColor: Colors.grey.withValues(alpha: 0.2), valueColor: const AlwaysStoppedAnimation( AppColors.primary, ), ), ), const SizedBox(width: 8), Text( '$currentValue/${achievement.requiredValue}', style: const TextStyle( fontFamily: 'Nunito', fontSize: 10, color: AppColors.textSecondary, ), ), ], ), ], ], ), ), const SizedBox(width: 8), // Status icon if (isUnlocked) const Icon(Icons.check_circle, color: AppColors.success, size: 24) else const Icon( Icons.lock_outline, color: AppColors.textSecondary, size: 24, ), ], ), ); } /// 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; } } }