diff --git a/analyze_output.txt b/analyze_output.txt index 3bce1be..ab269ef 100644 Binary files a/analyze_output.txt and b/analyze_output.txt differ diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index a64de9b..8aaa314 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -109,21 +109,24 @@ class ApiClient { bool requiresAuth = true, }) async { // build final query params including auth if needed - final Map finalParams = {}; + final originalUri = Uri.parse(url); + final queryParams = {...originalUri.queryParameters}; if (requiresAuth) { final token = await TokenStorage.getToken(); final username = await TokenStorage.getUsername(); if (token != null && username != null) { - finalParams['token'] = token; - finalParams['username'] = username; + queryParams['token'] = token; + queryParams['username'] = username; } // Guest mode: proceed without token — let backend decide } - if (params != null) finalParams.addAll(params); + if (params != null) { + queryParams.addAll(params.map((k, v) => MapEntry(k, v?.toString() ?? ''))); + } - final uri = Uri.parse(url).replace(queryParameters: finalParams.map((k, v) => MapEntry(k, v?.toString()))); + final uri = originalUri.replace(queryParameters: queryParams); late http.Response response; try { @@ -133,7 +136,7 @@ class ApiClient { throw Exception('Network error: $e'); } - return _handleResponse(url, response, finalParams); + return _handleResponse(url, response, queryParams); } // --------------------------------------------------------------------------- diff --git a/lib/features/gamification/models/gamification_models.dart b/lib/features/gamification/models/gamification_models.dart index ccabc0c..6971aef 100644 --- a/lib/features/gamification/models/gamification_models.dart +++ b/lib/features/gamification/models/gamification_models.dart @@ -1,6 +1,8 @@ // lib/features/gamification/models/gamification_models.dart // Data models matching TechDocs v2 DB schema for the Contributor Module. +import 'package:flutter/foundation.dart'; + // --------------------------------------------------------------------------- // Tier enum — matches PRD v3 §4.4 thresholds (based on Lifetime EP) // --------------------------------------------------------------------------- @@ -68,13 +70,21 @@ int tierStartEp(ContributorTier tier) { // --------------------------------------------------------------------------- class UserGamificationProfile { final String userId; - final int lifetimeEp; // Never resets. Used for tiers + leaderboard. - final int currentEp; // Liquid EP accumulated this month. - final int currentRp; // Spendable Reward Points. + final String username; + final String? avatarUrl; + final String? district; + final String? eventifyId; + final int lifetimeEp; // Never resets. Used for tiers + leaderboard. + final int currentEp; // Liquid EP accumulated this month. + final int currentRp; // Spendable Reward Points. final ContributorTier tier; const UserGamificationProfile({ required this.userId, + required this.username, + this.avatarUrl, + this.district, + this.eventifyId, required this.lifetimeEp, required this.currentEp, required this.currentRp, @@ -82,12 +92,17 @@ class UserGamificationProfile { }); factory UserGamificationProfile.fromJson(Map json) { - final ep = (json['lifetime_ep'] as int?) ?? 0; + debugPrint('Mapping UserGamificationProfile from JSON: $json'); + final ep = (json['lifetime_ep'] as int?) ?? (json['points'] as int?) ?? (json['total_points'] as int?) ?? 0; return UserGamificationProfile( - userId: json['user_id'] as String? ?? '', + userId: (json['user_id'] ?? json['email'] ?? json['userId'] ?? '').toString(), + username: (json['username'] ?? json['name'] ?? json['full_name'] ?? json['display_name'] ?? '').toString(), + avatarUrl: json['profile_image'] as String? ?? json['avatar_url'] as String? ?? json['profile_pic'] as String?, + district: json['district'] as String? ?? json['location'] as String?, + eventifyId: (json['eventify_id'] ?? json['eventifyId'] ?? json['id'] ?? '').toString(), lifetimeEp: ep, - currentEp: (json['current_ep'] as int?) ?? 0, - currentRp: (json['current_rp'] as int?) ?? 0, + currentEp: (json['current_ep'] as int?) ?? (json['monthly_points'] as int?) ?? (json['points_this_month'] as int?) ?? 0, + currentRp: (json['current_rp'] as int?) ?? (json['reward_points'] as int?) ?? (json['rp'] as int?) ?? 0, tier: tierFromEp(ep), ); } diff --git a/lib/features/gamification/providers/gamification_provider.dart b/lib/features/gamification/providers/gamification_provider.dart index 74eeaf4..eb75b5d 100644 --- a/lib/features/gamification/providers/gamification_provider.dart +++ b/lib/features/gamification/providers/gamification_provider.dart @@ -36,8 +36,10 @@ class GamificationProvider extends ChangeNotifier { // 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) { + debugPrint('GamificationProvider.loadAll(force: $force) called'); + // Skip if recently loaded (within 2 minutes) unless forced or profile is null + if (!force && profile != null && _lastLoadTime != null && DateTime.now().difference(_lastLoadTime!) < _loadTtl) { + debugPrint('GamificationProvider.loadAll skipped due to TTL'); return; } @@ -46,10 +48,20 @@ class GamificationProvider extends ChangeNotifier { notifyListeners(); try { + debugPrint('GamificationProvider: Requesting dashboard, leaderboard, etc...'); final results = await Future.wait([ _service.getDashboard().catchError((e) { debugPrint('Dashboard error: $e'); - return const DashboardResponse(profile: UserGamificationProfile(userId: '', lifetimeEp: 0, currentEp: 0, currentRp: 0, tier: ContributorTier.BRONZE)); + return const DashboardResponse( + profile: UserGamificationProfile( + userId: '', + username: '', + lifetimeEp: 0, + currentEp: 0, + currentRp: 0, + tier: ContributorTier.BRONZE, + ), + ); }), _service.getLeaderboard(district: leaderboardDistrict, timePeriod: leaderboardTimePeriod).catchError((e) { debugPrint('Leaderboard error: $e'); @@ -179,6 +191,10 @@ class GamificationProvider extends ChangeNotifier { if (profile != null) { profile = UserGamificationProfile( userId: profile!.userId, + username: profile!.username, + avatarUrl: profile!.avatarUrl, + district: profile!.district, + eventifyId: profile!.eventifyId, lifetimeEp: profile!.lifetimeEp, currentEp: profile!.currentEp, currentRp: profile!.currentRp - item.rpCost, @@ -195,6 +211,10 @@ class GamificationProvider extends ChangeNotifier { if (profile != null) { profile = UserGamificationProfile( userId: profile!.userId, + username: profile!.username, + avatarUrl: profile!.avatarUrl, + district: profile!.district, + eventifyId: profile!.eventifyId, lifetimeEp: profile!.lifetimeEp, currentEp: profile!.currentEp, currentRp: profile!.currentRp + item.rpCost, diff --git a/lib/screens/contribute_screen.dart b/lib/screens/contribute_screen.dart index 4e4b348..1522466 100644 --- a/lib/screens/contribute_screen.dart +++ b/lib/screens/contribute_screen.dart @@ -1785,7 +1785,7 @@ class _ContributeScreenState extends State try { final data = { - 'title': _titleCtl.text.trim(), + 'event_name': _titleCtl.text.trim(), 'category': _selectedCategory, 'district': _selectedDistrict, 'date': _selectedDate!.toIso8601String(), diff --git a/lib/screens/profile_screen.dart b/lib/screens/profile_screen.dart index aa78c74..90ccbb4 100644 --- a/lib/screens/profile_screen.dart +++ b/lib/screens/profile_screen.dart @@ -111,7 +111,7 @@ class _ProfileScreenState extends State // Load gamification data for profile EP cards WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) context.read().loadAll(); + if (mounted) context.read().loadAll(force: true); }); } @@ -829,7 +829,7 @@ class _ProfileScreenState extends State // Username Text( - _username, + (p?.username.isNotEmpty == true) ? p!.username : _username, style: const TextStyle( color: Colors.white, fontSize: 24, @@ -840,10 +840,11 @@ class _ProfileScreenState extends State const SizedBox(height: 12), // Eventify ID Badge - if (_eventifyId != null) + if ((p?.eventifyId ?? _eventifyId) != null) GestureDetector( onTap: () { - Clipboard.setData(ClipboardData(text: _eventifyId!)); + final id = p?.eventifyId ?? _eventifyId!; + Clipboard.setData(ClipboardData(text: id)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('ID copied to clipboard')), ); @@ -861,7 +862,7 @@ class _ProfileScreenState extends State const Icon(Icons.copy, size: 14, color: Colors.white70), const SizedBox(width: 8), Text( - _eventifyId!, + p?.eventifyId ?? _eventifyId!, style: const TextStyle( color: Colors.white, fontSize: 13, @@ -882,7 +883,7 @@ class _ProfileScreenState extends State const Icon(Icons.location_on_outlined, color: Colors.white, size: 18), const SizedBox(width: 4), Text( - _district ?? 'No district selected', + p?.district ?? _district ?? 'No district selected', style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w400), ), ],