// lib/features/gamification/providers/gamification_provider.dart import 'package:flutter/foundation.dart'; import '../../../core/utils/error_utils.dart'; import '../models/gamification_models.dart'; import '../services/gamification_service.dart'; class GamificationProvider extends ChangeNotifier { final GamificationService _service = GamificationService(); // State UserGamificationProfile? profile; List leaderboard = []; List shopItems = []; List achievements = []; List submissions = []; CurrentUserStats? currentUserStats; int totalParticipants = 0; // Leaderboard filters — matches web version String leaderboardDistrict = 'Overall Kerala'; String leaderboardTimePeriod = 'all_time'; // 'all_time' | 'this_month' bool isLoading = false; String? error; // TTL guard — prevents redundant API calls from multiple screens DateTime? _lastLoadTime; static const _loadTtl = Duration(minutes: 2); // --------------------------------------------------------------------------- // Load everything at once (called when ContributeScreen or ProfileScreen mounts) // --------------------------------------------------------------------------- Future loadAll({bool force = false}) async { // Skip if recently loaded (within 2 minutes) unless forced if (!force && _lastLoadTime != null && DateTime.now().difference(_lastLoadTime!) < _loadTtl) { return; } isLoading = true; error = null; notifyListeners(); try { final results = await Future.wait([ _service.getDashboard(), _service.getLeaderboard(district: leaderboardDistrict, timePeriod: leaderboardTimePeriod), _service.getShopItems(), _service.getAchievements(), ]); final dashboard = results[0] as DashboardResponse; profile = dashboard.profile; submissions = dashboard.submissions; final lbResponse = results[1] as LeaderboardResponse; leaderboard = lbResponse.entries; currentUserStats = lbResponse.currentUser; totalParticipants = lbResponse.totalParticipants; shopItems = results[2] as List; // Prefer achievements from dashboard API; fall back to getAchievements() final dashAchievements = dashboard.achievements; final fetchedAchievements = results[3] as List; achievements = dashAchievements.isNotEmpty ? dashAchievements : fetchedAchievements; _lastLoadTime = DateTime.now(); } catch (e) { error = userFriendlyError(e); } finally { isLoading = false; notifyListeners(); } } // --------------------------------------------------------------------------- // Change district filter // --------------------------------------------------------------------------- Future setDistrict(String district) async { if (leaderboardDistrict == district) return; leaderboardDistrict = district; notifyListeners(); try { final response = await _service.getLeaderboard(district: district, timePeriod: leaderboardTimePeriod); leaderboard = response.entries; currentUserStats = response.currentUser; totalParticipants = response.totalParticipants; } catch (e) { error = userFriendlyError(e); } notifyListeners(); } // --------------------------------------------------------------------------- // Change time period filter // --------------------------------------------------------------------------- Future setTimePeriod(String period) async { if (leaderboardTimePeriod == period) return; leaderboardTimePeriod = period; notifyListeners(); try { final response = await _service.getLeaderboard(district: leaderboardDistrict, timePeriod: period); leaderboard = response.entries; currentUserStats = response.currentUser; totalParticipants = response.totalParticipants; } catch (e) { error = userFriendlyError(e); } notifyListeners(); } // --------------------------------------------------------------------------- // Redeem a shop item — deducts RP locally optimistically, returns voucher code // --------------------------------------------------------------------------- Future redeemItem(String itemId) async { final item = shopItems.firstWhere((s) => s.id == itemId); // Optimistically deduct RP if (profile != null) { profile = UserGamificationProfile( userId: profile!.userId, lifetimeEp: profile!.lifetimeEp, currentEp: profile!.currentEp, currentRp: profile!.currentRp - item.rpCost, tier: profile!.tier, ); notifyListeners(); } try { final record = await _service.redeemItem(itemId); return record.voucherCode; } catch (e) { // Rollback on failure if (profile != null) { profile = UserGamificationProfile( userId: profile!.userId, lifetimeEp: profile!.lifetimeEp, currentEp: profile!.currentEp, currentRp: profile!.currentRp + item.rpCost, tier: profile!.tier, ); notifyListeners(); } rethrow; } } // --------------------------------------------------------------------------- // Submit a contribution // --------------------------------------------------------------------------- Future submitContribution(Map data) async { await _service.submitContribution(data); } }