// lib/features/gamification/models/gamification_models.dart // Data models matching TechDocs v2 DB schema for the Contributor Module. // --------------------------------------------------------------------------- // Tier enum — matches PRD v3 §4.4 thresholds (based on Lifetime EP) // --------------------------------------------------------------------------- enum ContributorTier { BRONZE, SILVER, GOLD, PLATINUM, DIAMOND } /// Returns the correct tier for a given lifetime EP total. ContributorTier tierFromEp(int lifetimeEp) { if (lifetimeEp >= 5000) return ContributorTier.DIAMOND; if (lifetimeEp >= 1500) return ContributorTier.PLATINUM; if (lifetimeEp >= 500) return ContributorTier.GOLD; if (lifetimeEp >= 100) return ContributorTier.SILVER; return ContributorTier.BRONZE; } /// Human-readable label for a tier. String tierLabel(ContributorTier tier) { switch (tier) { case ContributorTier.BRONZE: return 'Bronze'; case ContributorTier.SILVER: return 'Silver'; case ContributorTier.GOLD: return 'Gold'; case ContributorTier.PLATINUM: return 'Platinum'; case ContributorTier.DIAMOND: return 'Diamond'; } } /// EP threshold for next tier (used for progress bar). Returns null at max tier. int? nextTierThreshold(ContributorTier tier) { switch (tier) { case ContributorTier.BRONZE: return 100; case ContributorTier.SILVER: return 500; case ContributorTier.GOLD: return 1500; case ContributorTier.PLATINUM: return 5000; case ContributorTier.DIAMOND: return null; } } /// Lower EP bound for current tier (used for progress bar calculation). int tierStartEp(ContributorTier tier) { switch (tier) { case ContributorTier.BRONZE: return 0; case ContributorTier.SILVER: return 100; case ContributorTier.GOLD: return 500; case ContributorTier.PLATINUM: return 1500; case ContributorTier.DIAMOND: return 5000; } } // --------------------------------------------------------------------------- // UserGamificationProfile — mirrors the `UserGamificationProfile` DB table // --------------------------------------------------------------------------- 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 ContributorTier tier; const UserGamificationProfile({ required this.userId, required this.lifetimeEp, required this.currentEp, required this.currentRp, required this.tier, }); factory UserGamificationProfile.fromJson(Map json) { final ep = (json['lifetime_ep'] as int?) ?? 0; return UserGamificationProfile( userId: json['user_id'] as String? ?? '', lifetimeEp: ep, currentEp: (json['current_ep'] as int?) ?? 0, currentRp: (json['current_rp'] as int?) ?? 0, tier: tierFromEp(ep), ); } } // --------------------------------------------------------------------------- // LeaderboardEntry // --------------------------------------------------------------------------- class LeaderboardEntry { final int rank; final String username; final String? avatarUrl; final int lifetimeEp; final ContributorTier tier; final int eventsCount; final bool isCurrentUser; const LeaderboardEntry({ required this.rank, required this.username, this.avatarUrl, required this.lifetimeEp, required this.tier, required this.eventsCount, this.isCurrentUser = false, }); factory LeaderboardEntry.fromJson(Map json) { final ep = (json['lifetime_ep'] as int?) ?? 0; return LeaderboardEntry( rank: (json['rank'] as int?) ?? 0, username: json['username'] as String? ?? '', avatarUrl: json['avatar_url'] as String?, lifetimeEp: ep, tier: tierFromEp(ep), eventsCount: (json['events_count'] as int?) ?? 0, isCurrentUser: (json['is_current_user'] as bool?) ?? false, ); } } // --------------------------------------------------------------------------- // ShopItem — mirrors `RedeemShopItem` table // --------------------------------------------------------------------------- class ShopItem { final String id; final String name; final String description; final int rpCost; final int stockQuantity; final String? imageUrl; const ShopItem({ required this.id, required this.name, required this.description, required this.rpCost, required this.stockQuantity, this.imageUrl, }); factory ShopItem.fromJson(Map json) { return ShopItem( id: json['id'] as String? ?? '', name: json['name'] as String? ?? '', description: json['description'] as String? ?? '', rpCost: (json['rp_cost'] as int?) ?? 0, stockQuantity: (json['stock_quantity'] as int?) ?? 0, imageUrl: json['image_url'] as String?, ); } } // --------------------------------------------------------------------------- // RedemptionRecord — mirrors `RedemptionHistory` table // --------------------------------------------------------------------------- class RedemptionRecord { final String id; final String itemId; final int rpSpent; final String voucherCode; final DateTime timestamp; const RedemptionRecord({ required this.id, required this.itemId, required this.rpSpent, required this.voucherCode, required this.timestamp, }); factory RedemptionRecord.fromJson(Map json) { return RedemptionRecord( id: json['id'] as String? ?? '', itemId: json['item_id'] as String? ?? '', rpSpent: (json['rp_spent'] as int?) ?? 0, voucherCode: json['voucher_code_issued'] as String? ?? '', timestamp: DateTime.tryParse(json['timestamp'] as String? ?? '') ?? DateTime.now(), ); } } // --------------------------------------------------------------------------- // AchievementBadge // --------------------------------------------------------------------------- class AchievementBadge { final String id; final String title; final String description; final String iconName; // maps to an IconData key final bool isUnlocked; final double progress; // 0.0 – 1.0 const AchievementBadge({ required this.id, required this.title, required this.description, required this.iconName, required this.isUnlocked, required this.progress, }); }