Files
Eventify-frontend/lib/features/gamification/models/gamification_models.dart
Sicherhaven 50caad21a5 release: bump version to 1.4(p) (versionCode 14)
- Update versionCode 12 → 14, versionName 1.3(p) → 1.4(p)
- Update pubspec.yaml version to 1.4.0+14
- Add CHANGELOG.md with full version history
- Update README.md: version badge + changelog section
- Desktop Contribute Dashboard rebuilt to match web version
  - Contributor Dashboard title, 3-tab nav (Contribute/Leaderboard/Achievements)
  - Two-column submit form, tier milestone progress bar
  - Desktop leaderboard with podium, filters, rank table (green points)
  - Desktop achievements 3-column badge grid
  - Inline Reward Shop with RP balance
- Gamification feature module (EP, RP, leaderboard, achievements, shop)
- Profile screen redesigned to match web app layout with animations
- Home screen bottom sheet date filter chips
- Updated API endpoints, login/event detail screens, theme colors
- Added Gilroy font suite, responsive layout improvements
2026-03-18 11:10:56 +05:30

213 lines
6.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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,
});
}