Files
Eventify-frontend/lib/features/gamification/models/gamification_models.dart

213 lines
6.4 KiB
Dart
Raw Normal View History

// 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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,
});
}